aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2019-09-03 20:33:00 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-09-03 20:33:00 +0200
commit3ca3e36cf7fce6b241e242a07cb4731760ee4265 (patch)
tree0796a3712aebbf695545892be19fc677e7acec8e /src
parentAutomatic i18n update (i18n.meetfranz.com) (diff)
parentUpdate en-US.json (diff)
downloadferdium-app-3ca3e36cf7fce6b241e242a07cb4731760ee4265.tar.gz
ferdium-app-3ca3e36cf7fce6b241e242a07cb4731760ee4265.tar.zst
ferdium-app-3ca3e36cf7fce6b241e242a07cb4731760ee4265.zip
Merge branch 'develop' into i18n
Diffstat (limited to 'src')
-rw-r--r--src/actions/index.js2
-rw-r--r--src/actions/user.js3
-rw-r--r--src/api/UserApi.js4
-rw-r--r--src/api/server/ServerApi.js16
-rw-r--r--src/components/TrialActivationInfoBar.js94
-rw-r--r--src/components/auth/AuthLayout.js6
-rw-r--r--src/components/auth/Pricing.js246
-rw-r--r--src/components/auth/Signup.js31
-rw-r--r--src/components/layout/AppLayout.js130
-rw-r--r--src/components/services/content/ServiceRestricted.js78
-rw-r--r--src/components/services/content/ServiceView.js25
-rw-r--r--src/components/services/content/Services.js48
-rw-r--r--src/components/settings/account/AccountDashboard.js197
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js8
-rw-r--r--src/components/settings/recipes/RecipeItem.js2
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js194
-rw-r--r--src/components/settings/services/EditServiceForm.js17
-rw-r--r--src/components/settings/services/ServicesDashboard.js2
-rw-r--r--src/components/settings/settings/EditSettingsForm.js6
-rw-r--r--src/components/settings/team/TeamDashboard.js10
-rw-r--r--src/components/subscription/SubscriptionForm.js226
-rw-r--r--src/components/subscription/SubscriptionPopup.js4
-rw-r--r--src/components/subscription/TrialForm.js114
-rw-r--r--src/components/ui/FeatureItem.js37
-rw-r--r--src/components/ui/FeatureList.js89
-rw-r--r--src/components/ui/Modal/index.js3
-rw-r--r--src/config.js36
-rw-r--r--src/containers/auth/AuthLayoutContainer.js10
-rw-r--r--src/containers/auth/PricingScreen.js40
-rw-r--r--src/containers/layout/AppLayoutContainer.js7
-rw-r--r--src/containers/settings/AccountScreen.js3
-rw-r--r--src/containers/settings/EditServiceScreen.js4
-rw-r--r--src/containers/settings/EditSettingsScreen.js6
-rw-r--r--src/containers/settings/RecipesScreen.js40
-rw-r--r--src/containers/settings/SettingsWindow.js35
-rw-r--r--src/containers/settings/TeamScreen.js1
-rw-r--r--src/containers/subscription/SubscriptionFormScreen.js99
-rw-r--r--src/containers/subscription/SubscriptionPopupScreen.js3
-rw-r--r--src/environment.js8
-rw-r--r--src/features/announcements/components/AnnouncementScreen.js2
-rw-r--r--src/features/basicAuth/Component.js1
-rw-r--r--src/features/communityRecipes/index.js28
-rw-r--r--src/features/communityRecipes/store.js31
-rw-r--r--src/features/delayApp/Component.js43
-rw-r--r--src/features/delayApp/index.js7
-rw-r--r--src/features/delayApp/styles.js1
-rw-r--r--src/features/serviceLimit/components/LimitReachedInfobox.js78
-rw-r--r--src/features/serviceLimit/index.js33
-rw-r--r--src/features/serviceLimit/store.js41
-rw-r--r--src/features/serviceProxy/index.js8
-rw-r--r--src/features/shareFranz/Component.js3
-rw-r--r--src/features/spellchecker/index.js8
-rw-r--r--src/features/todos/actions.js22
-rw-r--r--src/features/todos/components/TodosWebview.js237
-rw-r--r--src/features/todos/constants.js4
-rw-r--r--src/features/todos/containers/TodosScreen.js32
-rw-r--r--src/features/todos/index.js34
-rw-r--r--src/features/todos/preload.js23
-rw-r--r--src/features/todos/store.js147
-rw-r--r--src/features/workspaces/store.js27
-rw-r--r--src/helpers/plan-helpers.js35
-rw-r--r--src/i18n/locales/defaultMessages.json1501
-rw-r--r--src/i18n/locales/en-US.json86
-rw-r--r--src/i18n/messages/src/components/TrialActivationInfoBar.json15
-rw-r--r--src/i18n/messages/src/components/auth/Pricing.json101
-rw-r--r--src/i18n/messages/src/components/auth/Signup.json57
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json12
-rw-r--r--src/i18n/messages/src/components/services/content/ServiceRestricted.json67
-rw-r--r--src/i18n/messages/src/components/services/content/Services.json8
-rw-r--r--src/i18n/messages/src/components/settings/account/AccountDashboard.json124
-rw-r--r--src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json32
-rw-r--r--src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json116
-rw-r--r--src/i18n/messages/src/components/settings/services/EditServiceForm.json88
-rw-r--r--src/i18n/messages/src/components/settings/services/ServicesDashboard.json36
-rw-r--r--src/i18n/messages/src/components/subscription/SubscriptionForm.json164
-rw-r--r--src/i18n/messages/src/components/subscription/TrialForm.json93
-rw-r--r--src/i18n/messages/src/components/ui/FeatureList.json132
-rw-r--r--src/i18n/messages/src/features/delayApp/Component.json40
-rw-r--r--src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json15
-rw-r--r--src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json28
-rw-r--r--src/i18n/messages/src/features/shareFranz/Component.json28
-rw-r--r--src/i18n/messages/src/helpers/plan-helpers.json54
-rw-r--r--src/i18n/messages/src/helpers/pricing-helpers.json80
-rw-r--r--src/i18n/messages/src/lib/Menu.json307
-rw-r--r--src/index.html12
-rw-r--r--src/index.js29
-rw-r--r--src/lib/Menu.js104
-rw-r--r--src/models/Service.js15
-rw-r--r--src/models/User.js14
-rw-r--r--src/stores/AppStore.js29
-rw-r--r--src/stores/FeaturesStore.js6
-rw-r--r--src/stores/RecipesStore.js27
-rw-r--r--src/stores/ServicesStore.js58
-rw-r--r--src/stores/UIStore.js30
-rw-r--r--src/stores/UserStore.js45
-rw-r--r--src/stores/index.js6
-rw-r--r--src/stores/lib/Reaction.js6
-rw-r--r--src/styles/auth.scss2
-rw-r--r--src/styles/layout.scss14
-rw-r--r--src/styles/recipes.scss3
-rw-r--r--src/styles/reset.scss3
-rw-r--r--src/styles/settings.scss1
-rw-r--r--src/webview/contextMenu.js22
-rw-r--r--src/webview/spellchecker.js17
104 files changed, 4708 insertions, 1648 deletions
diff --git a/src/actions/index.js b/src/actions/index.js
index fc525afeb..336344d76 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -13,6 +13,7 @@ import settings from './settings';
13import requests from './requests'; 13import requests from './requests';
14import announcements from '../features/announcements/actions'; 14import announcements from '../features/announcements/actions';
15import workspaces from '../features/workspaces/actions'; 15import workspaces from '../features/workspaces/actions';
16import todos from '../features/todos/actions';
16 17
17const actions = Object.assign({}, { 18const actions = Object.assign({}, {
18 service, 19 service,
@@ -31,4 +32,5 @@ export default Object.assign(
31 defineActions(actions, PropTypes.checkPropTypes), 32 defineActions(actions, PropTypes.checkPropTypes),
32 { announcements }, 33 { announcements },
33 { workspaces }, 34 { workspaces },
35 { todos },
34); 36);
diff --git a/src/actions/user.js b/src/actions/user.js
index ccf1fa56a..5d7d9a899 100644
--- a/src/actions/user.js
+++ b/src/actions/user.js
@@ -17,6 +17,9 @@ export default {
17 retrievePassword: { 17 retrievePassword: {
18 email: PropTypes.string.isRequired, 18 email: PropTypes.string.isRequired,
19 }, 19 },
20 activateTrial: {
21 planId: PropTypes.string.isRequired,
22 },
20 invite: { 23 invite: {
21 invites: PropTypes.array.isRequired, 24 invites: PropTypes.array.isRequired,
22 }, 25 },
diff --git a/src/api/UserApi.js b/src/api/UserApi.js
index edfb88988..8ba8cd1e9 100644
--- a/src/api/UserApi.js
+++ b/src/api/UserApi.js
@@ -25,6 +25,10 @@ export default class UserApi {
25 return this.server.retrievePassword(email); 25 return this.server.retrievePassword(email);
26 } 26 }
27 27
28 activateTrial(data) {
29 return this.server.activateTrial(data);
30 }
31
28 invite(data) { 32 invite(data) {
29 return this.server.inviteUser(data); 33 return this.server.inviteUser(data);
30 } 34 }
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js
index a9ce202ff..f56c7b6e4 100644
--- a/src/api/server/ServerApi.js
+++ b/src/api/server/ServerApi.js
@@ -77,6 +77,20 @@ export default class ServerApi {
77 return u.token; 77 return u.token;
78 } 78 }
79 79
80 async activateTrial(data) {
81 const request = await sendAuthRequest(`${API_URL}/payment/trial`, {
82 method: 'POST',
83 body: JSON.stringify(data),
84 });
85 if (!request.ok) {
86 throw request;
87 }
88 const trial = await request.json();
89
90 debug('ServerApi::signup resolves', trial);
91 return true;
92 }
93
80 async inviteUser(data) { 94 async inviteUser(data) {
81 const request = await sendAuthRequest(`${API_URL}/invite`, { 95 const request = await sendAuthRequest(`${API_URL}/invite`, {
82 method: 'POST', 96 method: 'POST',
@@ -469,7 +483,7 @@ export default class ServerApi {
469 return services; 483 return services;
470 } 484 }
471 } catch (err) { 485 } catch (err) {
472 throw (new Error('ServerApi::getLegacyServices no config found')); 486 console.error('ServerApi::getLegacyServices no config found');
473 } 487 }
474 488
475 return []; 489 return [];
diff --git a/src/components/TrialActivationInfoBar.js b/src/components/TrialActivationInfoBar.js
new file mode 100644
index 000000000..acdf51d08
--- /dev/null
+++ b/src/components/TrialActivationInfoBar.js
@@ -0,0 +1,94 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4import ms from 'ms';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7
8import InfoBar from './ui/InfoBar';
9
10const messages = defineMessages({
11 message: {
12 id: 'infobar.trialActivated',
13 defaultMessage: '!!!Your trial was successfully activated. Happy messaging!',
14 },
15});
16
17const styles = {
18 notification: {
19 height: 'auto',
20 position: 'absolute',
21 top: -50,
22 transition: 'top 0.3s',
23 zIndex: 300,
24 width: 'calc(100% - 300px)',
25 },
26 show: {
27 top: 0,
28 },
29};
30
31@injectSheet(styles)
32class TrialActivationInfoBar extends Component {
33 static propTypes = {
34 // eslint-disable-next-line
35 classes: PropTypes.object.isRequired,
36 };
37
38 static contextTypes = {
39 intl: intlShape,
40 };
41
42 state = {
43 showing: false,
44 removed: false,
45 }
46
47 componentDidMount() {
48 setTimeout(() => {
49 this.setState({
50 showing: true,
51 });
52 }, 0);
53
54 setTimeout(() => {
55 this.setState({
56 showing: false,
57 });
58 }, ms('6s'));
59
60 setTimeout(() => {
61 this.setState({
62 removed: true,
63 });
64 }, ms('7s'));
65 }
66
67 render() {
68 const { classes } = this.props;
69 const { showing, removed } = this.state;
70 const { intl } = this.context;
71
72 if (removed) return null;
73
74 return (
75 <div
76 className={classnames({
77 [classes.notification]: true,
78 [classes.show]: showing,
79 })}
80 >
81 <InfoBar
82 type="primary"
83 position="top"
84 sticky
85 >
86 <span className="mdi mdi-information" />
87 {intl.formatMessage(messages.message)}
88 </InfoBar>
89 </div>
90 );
91 }
92}
93
94export default TrialActivationInfoBar;
diff --git a/src/components/auth/AuthLayout.js b/src/components/auth/AuthLayout.js
index 3d43d4e5c..75a8cfc61 100644
--- a/src/components/auth/AuthLayout.js
+++ b/src/components/auth/AuthLayout.js
@@ -22,7 +22,6 @@ export default @observer class AuthLayout extends Component {
22 retryHealthCheck: PropTypes.func.isRequired, 22 retryHealthCheck: PropTypes.func.isRequired,
23 isHealthCheckLoading: PropTypes.bool.isRequired, 23 isHealthCheckLoading: PropTypes.bool.isRequired,
24 isFullScreen: PropTypes.bool.isRequired, 24 isFullScreen: PropTypes.bool.isRequired,
25 darkMode: PropTypes.bool.isRequired,
26 nextAppReleaseVersion: PropTypes.string, 25 nextAppReleaseVersion: PropTypes.string,
27 installAppUpdate: PropTypes.func.isRequired, 26 installAppUpdate: PropTypes.func.isRequired,
28 appUpdateIsDownloaded: PropTypes.bool.isRequired, 27 appUpdateIsDownloaded: PropTypes.bool.isRequired,
@@ -45,7 +44,6 @@ export default @observer class AuthLayout extends Component {
45 retryHealthCheck, 44 retryHealthCheck,
46 isHealthCheckLoading, 45 isHealthCheckLoading,
47 isFullScreen, 46 isFullScreen,
48 darkMode,
49 nextAppReleaseVersion, 47 nextAppReleaseVersion,
50 installAppUpdate, 48 installAppUpdate,
51 appUpdateIsDownloaded, 49 appUpdateIsDownloaded,
@@ -53,7 +51,7 @@ export default @observer class AuthLayout extends Component {
53 const { intl } = this.context; 51 const { intl } = this.context;
54 52
55 return ( 53 return (
56 <div className={darkMode ? 'theme__dark' : ''}> 54 <>
57 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 55 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
58 <div className="auth"> 56 <div className="auth">
59 {!isOnline && ( 57 {!isOnline && (
@@ -93,7 +91,7 @@ export default @observer class AuthLayout extends Component {
93 <img src="./assets/images/adlk.svg" alt="" /> 91 <img src="./assets/images/adlk.svg" alt="" />
94 </Link> 92 </Link>
95 </div> 93 </div>
96 </div> 94 </>
97 ); 95 );
98 } 96 }
99} 97}
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index 7ab14f429..cbeaaa5d9 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -1,40 +1,107 @@
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 } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5// import { Link } from 'react-router'; 5import injectSheet from 'react-jss';
6import { H2, Loader } from '@meetfranz/ui';
7import classnames from 'classnames';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureItem } from '../ui/FeatureItem';
11import { FeatureList } from '../ui/FeatureList';
6 12
7// import Button from '../ui/Button';
8import Loader from '../ui/Loader';
9import Appear from '../ui/effects/Appear';
10import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen';
11 13
12const messages = defineMessages({ 14const messages = defineMessages({
13 headline: { 15 headline: {
14 id: 'pricing.headline', 16 id: 'pricing.trial.headline',
15 defaultMessage: '!!!Support Franz', 17 defaultMessage: '!!!Franz Professional',
18 },
19 personalOffer: {
20 id: 'pricing.trial.subheadline',
21 defaultMessage: '!!!Your personal welcome offer:',
22 },
23 noStringsAttachedHeadline: {
24 id: 'pricing.trial.terms.headline',
25 defaultMessage: '!!!No strings attached',
26 },
27 noCreditCard: {
28 id: 'pricing.trial.terms.noCreditCard',
29 defaultMessage: '!!!No credit card required',
16 }, 30 },
17 monthlySupportLabel: { 31 automaticTrialEnd: {
18 id: 'pricing.support.label', 32 id: 'pricing.trial.terms.automaticTrialEnd',
19 defaultMessage: '!!!Select your support plan', 33 defaultMessage: '!!!Your free trial ends automatically after 14 days',
20 }, 34 },
21 submitButtonLabel: { 35 activationError: {
22 id: 'pricing.submit.label', 36 id: 'pricing.trial.error',
23 defaultMessage: '!!!Support the development of Franz', 37 defaultMessage: '!!!Sorry, we could not activate your trial!',
24 }, 38 },
25 skipPayment: { 39 ctaAccept: {
26 id: 'pricing.link.skipPayment', 40 id: 'pricing.trial.cta.accept',
27 defaultMessage: '!!!I don\'t want to support the development of Franz.', 41 defaultMessage: '!!!Yes, upgrade my account to Franz Professional',
42 },
43 ctaSkip: {
44 id: 'pricing.trial.cta.skip',
45 defaultMessage: '!!!Continue to Franz',
46 },
47 featuresHeadline: {
48 id: 'pricing.trial.features.headline',
49 defaultMessage: '!!!Franz Professional includes:',
28 }, 50 },
29}); 51});
30 52
31export default @observer class Signup extends Component { 53const styles = theme => ({
54 container: {
55 position: 'relative',
56 marginLeft: -150,
57 },
58 welcomeOffer: {
59 textAlign: 'center',
60 fontWeight: 'bold',
61 },
62 keyTerms: {
63 textAlign: 'center',
64 },
65 content: {
66 position: 'relative',
67 zIndex: 20,
68 },
69 featureContainer: {
70 width: 300,
71 position: 'absolute',
72 left: 'calc(100% / 2 + 225px)',
73 top: 155,
74 background: theme.signup.pricing.feature.background,
75 height: 'auto',
76 padding: 20,
77 borderTopRightRadius: theme.borderRadius,
78 borderBottomRightRadius: theme.borderRadius,
79 zIndex: 10,
80 },
81 featureItem: {
82 borderBottom: [1, 'solid', theme.signup.pricing.feature.border],
83 },
84 cta: {
85 marginTop: 40,
86 width: '100%',
87 },
88 skipLink: {
89 textAlign: 'center',
90 marginTop: 10,
91 },
92 error: {
93 margin: [20, 0, 0],
94 color: theme.styleTypes.danger.accent,
95 },
96});
97
98export default @observer @injectSheet(styles) class Signup extends Component {
32 static propTypes = { 99 static propTypes = {
33 donor: MobxPropTypes.objectOrObservableObject.isRequired, 100 onSubmit: PropTypes.func.isRequired,
34 isLoading: PropTypes.bool.isRequired, 101 isLoadingRequiredData: PropTypes.bool.isRequired,
35 isLoadingUser: PropTypes.bool.isRequired, 102 isActivatingTrial: PropTypes.bool.isRequired,
36 onCloseSubscriptionWindow: PropTypes.func.isRequired, 103 trialActivationError: PropTypes.bool.isRequired,
37 skipAction: PropTypes.func.isRequired, 104 classes: PropTypes.object.isRequired,
38 }; 105 };
39 106
40 static contextTypes = { 107 static contextTypes = {
@@ -43,70 +110,37 @@ export default @observer class Signup extends Component {
43 110
44 render() { 111 render() {
45 const { 112 const {
46 donor, 113 onSubmit,
47 isLoading, 114 isLoadingRequiredData,
48 isLoadingUser, 115 isActivatingTrial,
49 onCloseSubscriptionWindow, 116 trialActivationError,
50 skipAction, 117 classes,
51 } = this.props; 118 } = this.props;
52 const { intl } = this.context; 119 const { intl } = this.context;
53 120
54 return ( 121 return (
55 <div className="auth__scroll-container"> 122 <div className={classnames('auth__scroll-container', classes.container)}>
56 <div className="auth__container auth__container--signup"> 123 <div className={classnames('auth__container', 'auth__container--signup', classes.content)}>
57 <form className="franz-form auth__form"> 124 <form className="franz-form auth__form">
58 <img 125 {isLoadingRequiredData ? <Loader /> : (
59 src="./assets/images/sm.png" 126 <img
60 className="auth__logo auth__logo--sm" 127 src="./assets/images/sm.png"
61 alt="" 128 className="auth__logo auth__logo--sm"
62 /> 129 alt=""
130 />
131 )}
132 <p className={classes.welcomeOffer}>{intl.formatMessage(messages.personalOffer)}</p>
63 <h1>{intl.formatMessage(messages.headline)}</h1> 133 <h1>{intl.formatMessage(messages.headline)}</h1>
64 <div className="auth__letter"> 134 <div className="auth__letter">
65 {isLoadingUser && ( 135 <p>
66 <p>Loading</p> 136 We built Franz with a lot of effort, manpower and love,
67 )} 137 to boost up your messaging experience.
68 {!isLoadingUser && ( 138 <br />
69 donor.amount ? ( 139 </p>
70 <span> 140 <p>
71 <p> 141 Get the free 14 day Franz Professional trial and see your communication evolving.
72 Thank you so much for your previous donation of 142 <br />
73 {' '} 143 </p>
74 <strong>
75 $
76 {donor.amount}
77 </strong>
78 .
79 <br />
80 Your support allowed us to get where we are today.
81 <br />
82 </p>
83 <p>
84 As an early supporter, you get
85 {' '}
86 <strong>a lifetime premium supporter license</strong>
87 {' '}
88 without any
89 additional charges.
90 </p>
91 <p>
92 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
93 <br />
94 <br />
95 </p>
96 </span>
97 ) : (
98 <span>
99 <p>
100 We built Franz with a lot of effort, manpower and love,
101 to bring you the best messaging experience.
102 <br />
103 </p>
104 <p>
105 Getting a Franz Premium Supporter License will allow us to keep improving Franz for you.
106 </p>
107 </span>
108 )
109 )}
110 <p> 144 <p>
111 Thanks for being a hero. 145 Thanks for being a hero.
112 </p> 146 </p>
@@ -114,20 +148,48 @@ export default @observer class Signup extends Component {
114 <strong>Stefan Malzner</strong> 148 <strong>Stefan Malzner</strong>
115 </p> 149 </p>
116 </div> 150 </div>
117 <Loader loaded={!isLoading}> 151 <div className={classes.keyTerms}>
118 <Appear transitionName="slideDown"> 152 <H2>
119 <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span> 153 {intl.formatMessage(messages.noStringsAttachedHeadline)}
120 <SubscriptionForm 154 </H2>
121 onCloseWindow={onCloseSubscriptionWindow} 155 <ul className={classes.keyTermsList}>
122 showSkipOption 156 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
123 skipAction={skipAction} 157 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
124 hideInfo={Boolean(donor.amount)} 158 </ul>
125 skipButtonLabel={intl.formatMessage(messages.skipPayment)} 159 </div>
126 /> 160 {trialActivationError && (
127 </Appear> 161 <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p>
128 </Loader> 162 )}
163 <Button
164 label={intl.formatMessage(messages.ctaAccept)}
165 className={classes.cta}
166 onClick={onSubmit}
167 busy={isActivatingTrial}
168 disabled={isLoadingRequiredData || isActivatingTrial}
169 />
170 <p className={classes.skipLink}>
171 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a>
172 </p>
129 </form> 173 </form>
130 </div> 174 </div>
175 <div className={classes.featureContainer}>
176 <H2>
177 {intl.formatMessage(messages.featuresHeadline)}
178 </H2>
179 {/* <ul className={classes.features}>
180 <FeatureItem name="Add unlimited services" className={classes.featureItem} />
181 <FeatureItem name="Spellchecker support" className={classes.featureItem} />
182 <FeatureItem name="Workspaces" className={classes.featureItem} />
183 <FeatureItem name="Add Custom Websites" className={classes.featureItem} />
184 <FeatureItem name="On-premise & other Hosted Services" className={classes.featureItem} />
185 <FeatureItem name="Install 3rd party services" className={classes.featureItem} />
186 <FeatureItem name="Service Proxies" className={classes.featureItem} />
187 <FeatureItem name="Team Management" className={classes.featureItem} />
188 <FeatureItem name="No Waiting Screens" className={classes.featureItem} />
189 <FeatureItem name="Forever ad-free" className={classes.featureItem} />
190 </ul> */}
191 <FeatureList />
192 </div>
131 </div> 193 </div>
132 ); 194 );
133 } 195 }
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index d9b83eeb8..0499d764b 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -7,7 +7,6 @@ import { isDevMode, useLiveAPI } from '../../environment';
7import Form from '../../lib/Form'; 7import Form from '../../lib/Form';
8import { required, email, minLength } from '../../helpers/validation-helpers'; 8import { required, email, minLength } from '../../helpers/validation-helpers';
9import Input from '../ui/Input'; 9import Input from '../ui/Input';
10import Radio from '../ui/Radio';
11import Button from '../ui/Button'; 10import Button from '../ui/Button';
12import Link from '../ui/Link'; 11import Link from '../ui/Link';
13import Infobox from '../ui/Infobox'; 12import Infobox from '../ui/Infobox';
@@ -31,10 +30,10 @@ const messages = defineMessages({
31 id: 'signup.email.label', 30 id: 'signup.email.label',
32 defaultMessage: '!!!Email address', 31 defaultMessage: '!!!Email address',
33 }, 32 },
34 companyLabel: { 33 // companyLabel: {
35 id: 'signup.company.label', 34 // id: 'signup.company.label',
36 defaultMessage: '!!!Company', 35 // defaultMessage: '!!!Company',
37 }, 36 // },
38 passwordLabel: { 37 passwordLabel: {
39 id: 'signup.password.label', 38 id: 'signup.password.label',
40 defaultMessage: '!!!Password', 39 defaultMessage: '!!!Password',
@@ -79,20 +78,6 @@ export default @observer class Signup extends Component {
79 78
80 form = new Form({ 79 form = new Form({
81 fields: { 80 fields: {
82 accountType: {
83 value: 'individual',
84 validators: [required],
85 options: [{
86 value: 'individual',
87 label: 'Individual',
88 }, {
89 value: 'non-profit',
90 label: 'Non-Profit',
91 }, {
92 value: 'company',
93 label: 'Company',
94 }],
95 },
96 firstname: { 81 firstname: {
97 label: this.context.intl.formatMessage(messages.firstnameLabel), 82 label: this.context.intl.formatMessage(messages.firstnameLabel),
98 value: '', 83 value: '',
@@ -108,10 +93,6 @@ export default @observer class Signup extends Component {
108 value: '', 93 value: '',
109 validators: [required, email], 94 validators: [required, email],
110 }, 95 },
111 organization: {
112 label: this.context.intl.formatMessage(messages.companyLabel),
113 value: '', // TODO: make required when accountType: company
114 },
115 password: { 96 password: {
116 label: this.context.intl.formatMessage(messages.passwordLabel), 97 label: this.context.intl.formatMessage(messages.passwordLabel),
117 value: '', 98 value: '',
@@ -151,7 +132,6 @@ export default @observer class Signup extends Component {
151 In Dev Mode your data is not persistent. Please use the live app for accesing the production API. 132 In Dev Mode your data is not persistent. Please use the live app for accesing the production API.
152 </Infobox> 133 </Infobox>
153 )} 134 )}
154 <Radio field={form.$('accountType')} showLabel={false} />
155 <div className="grid__row"> 135 <div className="grid__row">
156 <Input field={form.$('firstname')} focus /> 136 <Input field={form.$('firstname')} focus />
157 <Input field={form.$('lastname')} /> 137 <Input field={form.$('lastname')} />
@@ -162,9 +142,6 @@ export default @observer class Signup extends Component {
162 showPasswordToggle 142 showPasswordToggle
163 scorePassword 143 scorePassword
164 /> 144 />
165 {form.$('accountType').value === 'company' && (
166 <Input field={form.$('organization')} />
167 )}
168 {error.code === 'email-duplicate' && ( 145 {error.code === 'email-duplicate' && (
169 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p> 146 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p>
170 )} 147 )}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 499bc097a..941e60bfd 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -17,6 +17,8 @@ import { isWindows } from '../../environment';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; 17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces'; 18import { workspaceStore } from '../../features/workspaces';
19import AppUpdateInfoBar from '../AppUpdateInfoBar'; 19import AppUpdateInfoBar from '../AppUpdateInfoBar';
20import TrialActivationInfoBar from '../TrialActivationInfoBar';
21import Todos from '../../features/todos/containers/TodosScreen';
20 22
21function createMarkup(HTMLString) { 23function createMarkup(HTMLString) {
22 return { __html: HTMLString }; 24 return { __html: HTMLString };
@@ -39,7 +41,8 @@ const messages = defineMessages({
39 41
40const styles = theme => ({ 42const styles = theme => ({
41 appContent: { 43 appContent: {
42 width: `calc(100% + ${theme.workspaces.drawer.width}px)`, 44 // width: `calc(100% + ${theme.workspaces.drawer.width}px)`,
45 width: '100%',
43 transition: 'transform 0.5s ease', 46 transition: 'transform 0.5s ease',
44 transform() { 47 transform() {
45 return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`; 48 return workspaceStore.isWorkspaceDrawerOpen ? 'translateX(0)' : `translateX(-${theme.workspaces.drawer.width}px)`;
@@ -57,7 +60,6 @@ class AppLayout extends Component {
57 services: PropTypes.element.isRequired, 60 services: PropTypes.element.isRequired,
58 children: PropTypes.element, 61 children: PropTypes.element,
59 news: MobxPropTypes.arrayOrObservableArray.isRequired, 62 news: MobxPropTypes.arrayOrObservableArray.isRequired,
60 // isOnline: PropTypes.bool.isRequired,
61 showServicesUpdatedInfoBar: PropTypes.bool.isRequired, 63 showServicesUpdatedInfoBar: PropTypes.bool.isRequired,
62 appUpdateIsDownloaded: PropTypes.bool.isRequired, 64 appUpdateIsDownloaded: PropTypes.bool.isRequired,
63 nextAppReleaseVersion: PropTypes.string, 65 nextAppReleaseVersion: PropTypes.string,
@@ -68,8 +70,8 @@ class AppLayout extends Component {
68 areRequiredRequestsSuccessful: PropTypes.bool.isRequired, 70 areRequiredRequestsSuccessful: PropTypes.bool.isRequired,
69 retryRequiredRequests: PropTypes.func.isRequired, 71 retryRequiredRequests: PropTypes.func.isRequired,
70 areRequiredRequestsLoading: PropTypes.bool.isRequired, 72 areRequiredRequestsLoading: PropTypes.bool.isRequired,
71 darkMode: PropTypes.bool.isRequired,
72 isDelayAppScreenVisible: PropTypes.bool.isRequired, 73 isDelayAppScreenVisible: PropTypes.bool.isRequired,
74 hasActivatedTrial: PropTypes.bool.isRequired,
73 }; 75 };
74 76
75 static defaultProps = { 77 static defaultProps = {
@@ -89,7 +91,6 @@ class AppLayout extends Component {
89 sidebar, 91 sidebar,
90 services, 92 services,
91 children, 93 children,
92 // isOnline,
93 news, 94 news,
94 showServicesUpdatedInfoBar, 95 showServicesUpdatedInfoBar,
95 appUpdateIsDownloaded, 96 appUpdateIsDownloaded,
@@ -101,78 +102,71 @@ class AppLayout extends Component {
101 areRequiredRequestsSuccessful, 102 areRequiredRequestsSuccessful,
102 retryRequiredRequests, 103 retryRequiredRequests,
103 areRequiredRequestsLoading, 104 areRequiredRequestsLoading,
104 darkMode,
105 isDelayAppScreenVisible, 105 isDelayAppScreenVisible,
106 hasActivatedTrial,
106 } = this.props; 107 } = this.props;
107 108
108 const { intl } = this.context; 109 const { intl } = this.context;
109 110
110 return ( 111 return (
111 <ErrorBoundary> 112 <ErrorBoundary>
112 <div className={(darkMode ? 'theme__dark' : '')}> 113 <div className="app">
113 <div className="app"> 114 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />}
114 {isWindows && !isFullScreen && <TitleBar menu={window.franz.menu.template} icon="assets/images/logo.svg" />} 115 <div className={`app__content ${classes.appContent}`}>
115 <div className={`app__content ${classes.appContent}`}> 116 {workspacesDrawer}
116 {workspacesDrawer} 117 {sidebar}
117 {sidebar} 118 <div className="app__service">
118 <div className="app__service"> 119 <WorkspaceSwitchingIndicator />
119 <WorkspaceSwitchingIndicator /> 120 {news.length > 0 && news.map(item => (
120 {news.length > 0 && news.map(item => ( 121 <InfoBar
121 <InfoBar 122 key={item.id}
122 key={item.id} 123 position="top"
123 position="top" 124 type={item.type}
124 type={item.type} 125 sticky={item.sticky}
125 sticky={item.sticky} 126 onHide={() => removeNewsItem({ newsId: item.id })}
126 onHide={() => removeNewsItem({ newsId: item.id })} 127 >
127 > 128 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
128 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 129 </InfoBar>
129 </InfoBar> 130 ))}
130 ))} 131 {hasActivatedTrial && (
131 {/* {!isOnline && ( 132 <TrialActivationInfoBar />
132 <InfoBar 133 )}
133 type="danger" 134 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
134 sticky 135 <InfoBar
135 > 136 type="danger"
136 <span className="mdi mdi-flash" /> 137 ctaLabel="Try again"
137 {intl.formatMessage(globalMessages.notConnectedToTheInternet)} 138 ctaLoading={areRequiredRequestsLoading}
138 </InfoBar> 139 sticky
139 )} */} 140 onClick={retryRequiredRequests}
140 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 141 >
141 <InfoBar 142 <span className="mdi mdi-flash" />
142 type="danger" 143 {intl.formatMessage(messages.requiredRequestsFailed)}
143 ctaLabel="Try again" 144 </InfoBar>
144 ctaLoading={areRequiredRequestsLoading} 145 )}
145 sticky 146 {showServicesUpdatedInfoBar && (
146 onClick={retryRequiredRequests} 147 <InfoBar
147 > 148 type="primary"
148 <span className="mdi mdi-flash" /> 149 ctaLabel={intl.formatMessage(messages.buttonReloadServices)}
149 {intl.formatMessage(messages.requiredRequestsFailed)} 150 onClick={reloadServicesAfterUpdate}
150 </InfoBar> 151 sticky
151 )} 152 >
152 {showServicesUpdatedInfoBar && ( 153 <span className="mdi mdi-power-plug" />
153 <InfoBar 154 {intl.formatMessage(messages.servicesUpdated)}
154 type="primary" 155 </InfoBar>
155 ctaLabel={intl.formatMessage(messages.buttonReloadServices)} 156 )}
156 onClick={reloadServicesAfterUpdate} 157 {appUpdateIsDownloaded && (
157 sticky 158 <AppUpdateInfoBar
158 > 159 nextAppReleaseVersion={nextAppReleaseVersion}
159 <span className="mdi mdi-power-plug" /> 160 onInstallUpdate={installAppUpdate}
160 {intl.formatMessage(messages.servicesUpdated)} 161 />
161 </InfoBar> 162 )}
162 )} 163 {isDelayAppScreenVisible && (<DelayApp />)}
163 {appUpdateIsDownloaded && ( 164 <BasicAuth />
164 <AppUpdateInfoBar 165 <ShareFranz />
165 nextAppReleaseVersion={nextAppReleaseVersion} 166 {services}
166 onInstallUpdate={installAppUpdate} 167 {children}
167 />
168 )}
169 {isDelayAppScreenVisible && (<DelayApp />)}
170 <BasicAuth />
171 <ShareFranz />
172 {services}
173 {children}
174 </div>
175 </div> 168 </div>
169 <Todos />
176 </div> 170 </div>
177 </div> 171 </div>
178 </ErrorBoundary> 172 </ErrorBoundary>
diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js
new file mode 100644
index 000000000..4b8d926aa
--- /dev/null
+++ b/src/components/services/content/ServiceRestricted.js
@@ -0,0 +1,78 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { serviceLimitStore } from '../../../features/serviceLimit';
7import Button from '../../ui/Button';
8import { RESTRICTION_TYPES } from '../../../models/Service';
9
10const messages = defineMessages({
11 headlineServiceLimit: {
12 id: 'service.restrictedHandler.serviceLimit.headline',
13 defaultMessage: '!!!You have reached your service limit.',
14 },
15 textServiceLimit: {
16 id: 'service.restrictedHandler.serviceLimit.text',
17 defaultMessage: '!!!Please upgrade your account to use more than {count} services.',
18 },
19 headlineCustomUrl: {
20 id: 'service.restrictedHandler.customUrl.headline',
21 defaultMessage: '!!!Franz Professional Plan required',
22 },
23 textCustomUrl: {
24 id: 'service.restrictedHandler.customUrl.text',
25 defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.',
26 },
27 action: {
28 id: 'service.restrictedHandler.action',
29 defaultMessage: '!!!Upgrade Account',
30 },
31});
32
33export default @observer class ServiceRestricted extends Component {
34 static propTypes = {
35 name: PropTypes.string.isRequired,
36 upgrade: PropTypes.func.isRequired,
37 type: PropTypes.number.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 countdownInterval = null;
45
46 countdownIntervalTimeout = 1000;
47
48 render() {
49 const {
50 name,
51 upgrade,
52 type,
53 } = this.props;
54 const { intl } = this.context;
55
56 return (
57 <div className="services__info-layer">
58 {type === RESTRICTION_TYPES.SERVICE_LIMIT && (
59 <>
60 <h1>{intl.formatMessage(messages.headlineServiceLimit)}</h1>
61 <p>{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}</p>
62 </>
63 )}
64 {type === RESTRICTION_TYPES.CUSTOM_URL && (
65 <>
66 <h1>{intl.formatMessage(messages.headlineCustomUrl)}</h1>
67 <p>{intl.formatMessage(messages.textCustomUrl)}</p>
68 </>
69 )}
70 <Button
71 label={intl.formatMessage(messages.action, { name })}
72 buttonType="inverted"
73 onClick={() => upgrade()}
74 />
75 </div>
76 );
77 }
78}
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 13148b9b3..f65f51346 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -10,6 +10,7 @@ import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler'; 10import WebviewCrashHandler from './WebviewCrashHandler';
11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; 11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
12import ServiceDisabled from './ServiceDisabled'; 12import ServiceDisabled from './ServiceDisabled';
13import ServiceRestricted from './ServiceRestricted';
13import ServiceWebview from './ServiceWebview'; 14import ServiceWebview from './ServiceWebview';
14 15
15export default @observer class ServiceView extends Component { 16export default @observer class ServiceView extends Component {
@@ -21,6 +22,7 @@ export default @observer class ServiceView extends Component {
21 edit: PropTypes.func.isRequired, 22 edit: PropTypes.func.isRequired,
22 enable: PropTypes.func.isRequired, 23 enable: PropTypes.func.isRequired,
23 isActive: PropTypes.bool, 24 isActive: PropTypes.bool,
25 upgrade: PropTypes.func.isRequired,
24 }; 26 };
25 27
26 static defaultProps = { 28 static defaultProps = {
@@ -72,6 +74,7 @@ export default @observer class ServiceView extends Component {
72 reload, 74 reload,
73 edit, 75 edit,
74 enable, 76 enable,
77 upgrade,
75 } = this.props; 78 } = this.props;
76 79
77 const webviewClasses = classnames({ 80 const webviewClasses = classnames({
@@ -99,7 +102,7 @@ export default @observer class ServiceView extends Component {
99 reload={reload} 102 reload={reload}
100 /> 103 />
101 )} 104 )}
102 {service.isEnabled && service.isLoading && service.isFirstLoad && ( 105 {service.isEnabled && service.isLoading && service.isFirstLoad && !service.isServiceAccessRestricted && (
103 <WebviewLoader 106 <WebviewLoader
104 loaded={false} 107 loaded={false}
105 name={service.name} 108 name={service.name}
@@ -126,11 +129,21 @@ export default @observer class ServiceView extends Component {
126 )} 129 )}
127 </Fragment> 130 </Fragment>
128 ) : ( 131 ) : (
129 <ServiceWebview 132 <>
130 service={service} 133 {service.isServiceAccessRestricted ? (
131 setWebviewReference={setWebviewReference} 134 <ServiceRestricted
132 detachService={detachService} 135 name={service.recipe.name}
133 /> 136 upgrade={upgrade}
137 type={service.restrictionType}
138 />
139 ) : (
140 <ServiceWebview
141 service={service}
142 setWebviewReference={setWebviewReference}
143 detachService={detachService}
144 />
145 )}
146 </>
134 )} 147 )}
135 {statusBar} 148 {statusBar}
136 </div> 149 </div>
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 8f8c38a11..73c27bfb6 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -3,6 +3,9 @@ import 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, intlShape } from 'react-intl';
6import Confetti from 'react-confetti';
7import ms from 'ms';
8import injectSheet from 'react-jss';
6 9
7import ServiceView from './ServiceView'; 10import ServiceView from './ServiceView';
8import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
@@ -18,7 +21,17 @@ const messages = defineMessages({
18 }, 21 },
19}); 22});
20 23
21export default @observer class Services extends Component { 24
25const styles = {
26 confettiContainer: {
27 position: 'absolute',
28 width: '100%',
29 zIndex: 9999,
30 pointerEvents: 'none',
31 },
32};
33
34export default @observer @injectSheet(styles) class Services extends Component {
22 static propTypes = { 35 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray, 36 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 37 setWebviewReference: PropTypes.func.isRequired,
@@ -28,6 +41,9 @@ export default @observer class Services extends Component {
28 reload: PropTypes.func.isRequired, 41 reload: PropTypes.func.isRequired,
29 openSettings: PropTypes.func.isRequired, 42 openSettings: PropTypes.func.isRequired,
30 update: PropTypes.func.isRequired, 43 update: PropTypes.func.isRequired,
44 userHasCompletedSignup: PropTypes.bool.isRequired,
45 hasActivatedTrial: PropTypes.bool.isRequired,
46 classes: PropTypes.object.isRequired,
31 }; 47 };
32 48
33 static defaultProps = { 49 static defaultProps = {
@@ -38,6 +54,18 @@ export default @observer class Services extends Component {
38 intl: intlShape, 54 intl: intlShape,
39 }; 55 };
40 56
57 state = {
58 showConfetti: true,
59 }
60
61 componentDidMount() {
62 window.setTimeout(() => {
63 this.setState({
64 showConfetti: false,
65 });
66 }, ms('8s'));
67 }
68
41 render() { 69 render() {
42 const { 70 const {
43 services, 71 services,
@@ -48,11 +76,28 @@ export default @observer class Services extends Component {
48 reload, 76 reload,
49 openSettings, 77 openSettings,
50 update, 78 update,
79 userHasCompletedSignup,
80 hasActivatedTrial,
81 classes,
51 } = this.props; 82 } = this.props;
83
84 const {
85 showConfetti,
86 } = this.state;
87
52 const { intl } = this.context; 88 const { intl } = this.context;
53 89
54 return ( 90 return (
55 <div className="services"> 91 <div className="services">
92 {(userHasCompletedSignup || hasActivatedTrial) && (
93 <div className={classes.confettiContainer}>
94 <Confetti
95 width={window.width}
96 height={window.height}
97 numberOfPieces={showConfetti ? 200 : 0}
98 />
99 </div>
100 )}
56 {services.length === 0 && ( 101 {services.length === 0 && (
57 <Appear 102 <Appear
58 timeout={1500} 103 timeout={1500}
@@ -89,6 +134,7 @@ export default @observer class Services extends Component {
89 }, 134 },
90 redirect: false, 135 redirect: false,
91 })} 136 })}
137 upgrade={() => openSettings({ path: 'user' })}
92 /> 138 />
93 ))} 139 ))}
94 </div> 140 </div>
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 3f6964b6b..900a83a78 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -1,14 +1,18 @@
1import React, { Component, Fragment } 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, intlShape } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { ProBadge } from '@meetfranz/ui'; 6import {
7 ProBadge, H1, H2,
8} from '@meetfranz/ui';
9import moment from 'moment';
7 10
8import Loader from '../../ui/Loader'; 11import Loader from '../../ui/Loader';
9import Button from '../../ui/Button'; 12import Button from '../../ui/Button';
10import Infobox from '../../ui/Infobox'; 13import Infobox from '../../ui/Infobox';
11import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; 14import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
15import { i18nPlanName } from '../../../helpers/plan-helpers';
12 16
13const messages = defineMessages({ 17const messages = defineMessages({
14 headline: { 18 headline: {
@@ -19,10 +23,6 @@ const messages = defineMessages({
19 id: 'settings.account.headlineSubscription', 23 id: 'settings.account.headlineSubscription',
20 defaultMessage: '!!!Your Subscription', 24 defaultMessage: '!!!Your Subscription',
21 }, 25 },
22 headlineUpgrade: {
23 id: 'settings.account.headlineUpgrade',
24 defaultMessage: '!!!Upgrade your Account',
25 },
26 headlineDangerZone: { 26 headlineDangerZone: {
27 id: 'settings.account.headlineDangerZone', 27 id: 'settings.account.headlineDangerZone',
28 defaultMessage: '!!Danger Zone', 28 defaultMessage: '!!Danger Zone',
@@ -31,6 +31,10 @@ const messages = defineMessages({
31 id: 'settings.account.manageSubscription.label', 31 id: 'settings.account.manageSubscription.label',
32 defaultMessage: '!!!Manage your subscription', 32 defaultMessage: '!!!Manage your subscription',
33 }, 33 },
34 upgradeAccountToPro: {
35 id: 'settings.account.upgradeToPro.label',
36 defaultMessage: '!!!Upgrade to Franz Professional',
37 },
34 accountTypeBasic: { 38 accountTypeBasic: {
35 id: 'settings.account.accountType.basic', 39 id: 'settings.account.accountType.basic',
36 defaultMessage: '!!!Basic Account', 40 defaultMessage: '!!!Basic Account',
@@ -71,21 +75,36 @@ const messages = defineMessages({
71 id: 'settings.account.deleteEmailSent', 75 id: 'settings.account.deleteEmailSent',
72 defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 76 defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
73 }, 77 },
78 trial: {
79 id: 'settings.account.trial',
80 defaultMessage: '!!!Free Trial',
81 },
82 yourLicense: {
83 id: 'settings.account.yourLicense',
84 defaultMessage: '!!!Your Franz License:',
85 },
86 trialEndsIn: {
87 id: 'settings.account.trialEndsIn',
88 defaultMessage: '!!!Your free trial ends in {duration}.',
89 },
90 trialUpdateBillingInformation: {
91 id: 'settings.account.trialUpdateBillingInfo',
92 defaultMessage: '!!!Please update your billing info to continue using {license} after your trial period.',
93 },
74}); 94});
75 95
76export default @observer class AccountDashboard extends Component { 96export default @observer class AccountDashboard extends Component {
77 static propTypes = { 97 static propTypes = {
78 user: MobxPropTypes.observableObject.isRequired, 98 user: MobxPropTypes.observableObject.isRequired,
79 isLoading: PropTypes.bool.isRequired, 99 isLoading: PropTypes.bool.isRequired,
80 isLoadingPlans: PropTypes.bool.isRequired,
81 userInfoRequestFailed: PropTypes.bool.isRequired, 100 userInfoRequestFailed: PropTypes.bool.isRequired,
82 retryUserInfoRequest: PropTypes.func.isRequired, 101 retryUserInfoRequest: PropTypes.func.isRequired,
83 onCloseSubscriptionWindow: PropTypes.func.isRequired,
84 deleteAccount: PropTypes.func.isRequired, 102 deleteAccount: PropTypes.func.isRequired,
85 isLoadingDeleteAccount: PropTypes.bool.isRequired, 103 isLoadingDeleteAccount: PropTypes.bool.isRequired,
86 isDeleteAccountSuccessful: PropTypes.bool.isRequired, 104 isDeleteAccountSuccessful: PropTypes.bool.isRequired,
87 openEditAccount: PropTypes.func.isRequired, 105 openEditAccount: PropTypes.func.isRequired,
88 openBilling: PropTypes.func.isRequired, 106 openBilling: PropTypes.func.isRequired,
107 upgradeToPro: PropTypes.func.isRequired,
89 openInvoices: PropTypes.func.isRequired, 108 openInvoices: PropTypes.func.isRequired,
90 }; 109 };
91 110
@@ -97,19 +116,24 @@ export default @observer class AccountDashboard extends Component {
97 const { 116 const {
98 user, 117 user,
99 isLoading, 118 isLoading,
100 isLoadingPlans,
101 userInfoRequestFailed, 119 userInfoRequestFailed,
102 retryUserInfoRequest, 120 retryUserInfoRequest,
103 onCloseSubscriptionWindow,
104 deleteAccount, 121 deleteAccount,
105 isLoadingDeleteAccount, 122 isLoadingDeleteAccount,
106 isDeleteAccountSuccessful, 123 isDeleteAccountSuccessful,
107 openEditAccount, 124 openEditAccount,
108 openBilling, 125 openBilling,
126 upgradeToPro,
109 openInvoices, 127 openInvoices,
110 } = this.props; 128 } = this.props;
111 const { intl } = this.context; 129 const { intl } = this.context;
112 130
131 let planName = '';
132
133 if (user.team && user.team.plan) {
134 planName = i18nPlanName(user.team.plan, intl);
135 }
136
113 return ( 137 return (
114 <div className="settings__main"> 138 <div className="settings__main">
115 <div className="settings__header"> 139 <div className="settings__header">
@@ -135,82 +159,113 @@ export default @observer class AccountDashboard extends Component {
135 )} 159 )}
136 160
137 {!userInfoRequestFailed && ( 161 {!userInfoRequestFailed && (
138 <Fragment> 162 <>
139 {!isLoading && ( 163 {!isLoading && (
140 <div className="account"> 164 <>
141 <div className="account__box account__box--flex"> 165 <div className="account">
142 <div className="account__avatar"> 166 <div className="account__box account__box--flex">
143 <img 167 <div className="account__avatar">
144 src="./assets/images/logo.svg" 168 <img
145 alt="" 169 src="./assets/images/logo.svg"
146 /> 170 alt=""
147 </div> 171 />
148 <div className="account__info"> 172 </div>
149 <h2> 173 <div className="account__info">
150 <span className="username">{`${user.firstname} ${user.lastname}`}</span> 174 <H1>
175 <span className="username">{`${user.firstname} ${user.lastname}`}</span>
176 {user.isPremium && (
177 <>
178 {' '}
179 <ProBadge />
180 </>
181 )}
182 </H1>
183 <p>
184 {user.organization && `${user.organization}, `}
185 {user.email}
186 </p>
151 {user.isPremium && ( 187 {user.isPremium && (
188 <div className="manage-user-links">
189 <Button
190 label={intl.formatMessage(messages.accountEditButton)}
191 className="franz-form__button--inverted"
192 onClick={openEditAccount}
193 />
194 </div>
195 )}
196 </div>
197 {!user.isPremium && (
198 <Button
199 label={intl.formatMessage(messages.accountEditButton)}
200 className="franz-form__button--inverted"
201 onClick={openEditAccount}
202 />
203 )}
204 </div>
205 </div>
206 {user.isPremium && user.isSubscriptionOwner && (
207 <div className="account">
208 <div className="account__box">
209 <H2>
210 {intl.formatMessage(messages.yourLicense)}
211 </H2>
212 <p>
213 {planName}
214 {user.team.isTrial && (
215 <>
216 {' – '}
217 {intl.formatMessage(messages.trial)}
218 </>
219 )}
220 </p>
221 {user.team.isTrial && (
152 <> 222 <>
153 {' '} 223 <br />
154 <ProBadge /> 224 <p>
155 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> 225 {intl.formatMessage(messages.trialEndsIn, {
226 duration: moment.duration(moment().diff(user.team.trialEnd)).humanize(),
227 })}
228 </p>
229 <p>
230 {intl.formatMessage(messages.trialUpdateBillingInformation, {
231 license: planName,
232 })}
233 </p>
156 </> 234 </>
157 )} 235 )}
158 </h2>
159 {user.organization && `${user.organization}, `}
160 {user.email}
161 {user.isPremium && (
162 <div className="manage-user-links"> 236 <div className="manage-user-links">
163 <Button 237 <Button
164 label={intl.formatMessage(messages.accountEditButton)} 238 label={intl.formatMessage(messages.upgradeAccountToPro)}
239 className="franz-form__button--primary"
240 onClick={upgradeToPro}
241 />
242 <Button
243 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
165 className="franz-form__button--inverted" 244 className="franz-form__button--inverted"
166 onClick={openEditAccount} 245 onClick={openBilling}
246 />
247 <Button
248 label={intl.formatMessage(messages.invoicesButton)}
249 className="franz-form__button--inverted"
250 onClick={openInvoices}
167 /> 251 />
168 {user.isSubscriptionOwner && (
169 <>
170 <Button
171 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
172 className="franz-form__button--inverted"
173 onClick={openBilling}
174 />
175 <Button
176 label={intl.formatMessage(messages.invoicesButton)}
177 className="franz-form__button--inverted"
178 onClick={openInvoices}
179 />
180 </>
181 )}
182 </div> 252 </div>
183 )} 253 </div>
184 </div> 254 </div>
185 {!user.isPremium && ( 255 )}
186 <Button 256 {!user.isPremium && (
187 label={intl.formatMessage(messages.accountEditButton)} 257 <div className="account franz-form">
188 className="franz-form__button--inverted" 258 <div className="account__box">
189 onClick={openEditAccount} 259 <SubscriptionForm />
190 /> 260 </div>
191 )}
192 </div>
193 </div>
194 )}
195
196 {!user.isPremium && (
197 isLoadingPlans ? (
198 <Loader />
199 ) : (
200 <div className="account franz-form">
201 <div className="account__box">
202 <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2>
203 <SubscriptionForm
204 onCloseWindow={onCloseSubscriptionWindow}
205 />
206 </div> 261 </div>
207 </div> 262 )}
208 ) 263 </>
209 )} 264 )}
210 265
211 <div className="account franz-form"> 266 <div className="account franz-form">
212 <div className="account__box"> 267 <div className="account__box">
213 <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> 268 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2>
214 {!isDeleteAccountSuccessful && ( 269 {!isDeleteAccountSuccessful && (
215 <div className="account__subscription"> 270 <div className="account__subscription">
216 <p>{intl.formatMessage(messages.deleteInfo)}</p> 271 <p>{intl.formatMessage(messages.deleteInfo)}</p>
@@ -227,7 +282,7 @@ export default @observer class AccountDashboard extends Component {
227 )} 282 )}
228 </div> 283 </div>
229 </div> 284 </div>
230 </Fragment> 285 </>
231 )} 286 )}
232 </div> 287 </div>
233 <ReactTooltip place="right" type="dark" effect="solid" /> 288 <ReactTooltip place="right" type="dark" effect="solid" />
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index df4b3b3b2..4696b82eb 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -8,6 +8,7 @@ import Link from '../../ui/Link';
8import { workspaceStore } from '../../../features/workspaces'; 8import { workspaceStore } from '../../../features/workspaces';
9import UIStore from '../../../stores/UIStore'; 9import UIStore from '../../../stores/UIStore';
10import UserStore from '../../../stores/UserStore'; 10import UserStore from '../../../stores/UserStore';
11import { serviceLimitStore } from '../../../features/serviceLimit';
11 12
12const messages = defineMessages({ 13const messages = defineMessages({
13 availableServices: { 14 availableServices: {
@@ -80,7 +81,12 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp
80 > 81 >
81 {intl.formatMessage(messages.yourServices)} 82 {intl.formatMessage(messages.yourServices)}
82 {' '} 83 {' '}
83 <span className="badge">{serviceCount}</span> 84 <span className="badge">
85 {serviceCount}
86 {serviceLimitStore.serviceLimit !== 0 && (
87 `/${serviceLimitStore.serviceLimit}`
88 )}
89 </span>
84 </Link> 90 </Link>
85 {workspaceStore.isFeatureEnabled ? ( 91 {workspaceStore.isFeatureEnabled ? (
86 <Link 92 <Link
diff --git a/src/components/settings/recipes/RecipeItem.js b/src/components/settings/recipes/RecipeItem.js
index 3bb0852b2..12e3775f6 100644
--- a/src/components/settings/recipes/RecipeItem.js
+++ b/src/components/settings/recipes/RecipeItem.js
@@ -19,7 +19,7 @@ export default @observer class RecipeItem extends Component {
19 className="recipe-teaser" 19 className="recipe-teaser"
20 onClick={onClick} 20 onClick={onClick}
21 > 21 >
22 {recipe.local && ( 22 {recipe.isDevRecipe && (
23 <span className="recipe-teaser__dev-badge">dev</span> 23 <span className="recipe-teaser__dev-badge">dev</span>
24 )} 24 )}
25 <img 25 <img
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index 00cd725cf..75e60b7ec 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -4,12 +4,17 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router'; 5import { Link } from 'react-router';
6 6
7import { Button, Input } from '@meetfranz/forms';
8import injectSheet from 'react-jss';
9import { H3, H2, ProBadge } from '@meetfranz/ui';
7import SearchInput from '../../ui/SearchInput'; 10import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
9import RecipeItem from './RecipeItem'; 12import RecipeItem from './RecipeItem';
10import Loader from '../../ui/Loader'; 13import Loader from '../../ui/Loader';
11import Appear from '../../ui/effects/Appear'; 14import Appear from '../../ui/effects/Appear';
12import { FRANZ_SERVICE_REQUEST } from '../../../config'; 15import { FRANZ_SERVICE_REQUEST } from '../../../config';
16import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
17import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
13 18
14const messages = defineMessages({ 19const messages = defineMessages({
15 headline: { 20 headline: {
@@ -28,9 +33,9 @@ const messages = defineMessages({
28 id: 'settings.recipes.all', 33 id: 'settings.recipes.all',
29 defaultMessage: '!!!All services', 34 defaultMessage: '!!!All services',
30 }, 35 },
31 devRecipes: { 36 customRecipes: {
32 id: 'settings.recipes.dev', 37 id: 'settings.recipes.custom',
33 defaultMessage: '!!!Development', 38 defaultMessage: '!!!Custom Services',
34 }, 39 },
35 nothingFound: { 40 nothingFound: {
36 id: 'settings.recipes.nothingFound', 41 id: 'settings.recipes.nothingFound',
@@ -44,9 +49,61 @@ const messages = defineMessages({
44 id: 'settings.recipes.missingService', 49 id: 'settings.recipes.missingService',
45 defaultMessage: '!!!Missing a service?', 50 defaultMessage: '!!!Missing a service?',
46 }, 51 },
52 customRecipeIntro: {
53 id: 'settings.recipes.customService.intro',
54 defaultMessage: '!!!To add a custom service, copy the recipe folder into:',
55 },
56 openFolder: {
57 id: 'settings.recipes.customService.openFolder',
58 defaultMessage: '!!!Open directory',
59 },
60 openDevDocs: {
61 id: 'settings.recipes.customService.openDevDocs',
62 defaultMessage: '!!!Developer Documentation',
63 },
64 headlineCustomRecipes: {
65 id: 'settings.recipes.customService.headline.customRecipes',
66 defaultMessage: '!!!Custom Service Recipes',
67 },
68 headlineCommunityRecipes: {
69 id: 'settings.recipes.customService.headline.communityRecipes',
70 defaultMessage: '!!!Community Services',
71 },
72 headlineDevRecipes: {
73 id: 'settings.recipes.customService.headline.devRecipes',
74 defaultMessage: '!!!Your Development Service Recipes',
75 },
47}); 76});
48 77
49export default @observer class RecipesDashboard extends Component { 78const styles = {
79 devRecipeIntroContainer: {
80 textAlign: 'center',
81 width: '100%',
82 height: 'auto',
83 margin: [40, 0],
84 },
85 path: {
86 marginTop: 20,
87
88 '& > div': {
89 fontFamily: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace',
90 },
91 },
92 actionContainer: {
93 '& button': {
94 margin: [0, 10],
95 },
96 },
97 devRecipeList: {
98 marginTop: 20,
99 height: 'auto',
100 },
101 proBadge: {
102 marginLeft: '10px !important',
103 },
104};
105
106export default @injectSheet(styles) @observer class RecipesDashboard extends Component {
50 static propTypes = { 107 static propTypes = {
51 recipes: MobxPropTypes.arrayOrObservableArray.isRequired, 108 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
52 isLoading: PropTypes.bool.isRequired, 109 isLoading: PropTypes.bool.isRequired,
@@ -55,12 +112,18 @@ export default @observer class RecipesDashboard extends Component {
55 searchRecipes: PropTypes.func.isRequired, 112 searchRecipes: PropTypes.func.isRequired,
56 resetSearch: PropTypes.func.isRequired, 113 resetSearch: PropTypes.func.isRequired,
57 serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired, 114 serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired,
58 devRecipesCount: PropTypes.number.isRequired,
59 searchNeedle: PropTypes.string, 115 searchNeedle: PropTypes.string,
116 recipeFilter: PropTypes.string,
117 recipeDirectory: PropTypes.string.isRequired,
118 openRecipeDirectory: PropTypes.func.isRequired,
119 openDevDocs: PropTypes.func.isRequired,
120 classes: PropTypes.object.isRequired,
121 isCommunityRecipesIncludedInCurrentPlan: PropTypes.bool.isRequired,
60 }; 122 };
61 123
62 static defaultProps = { 124 static defaultProps = {
63 searchNeedle: '', 125 searchNeedle: '',
126 recipeFilter: 'all',
64 } 127 }
65 128
66 static contextTypes = { 129 static contextTypes = {
@@ -76,16 +139,26 @@ export default @observer class RecipesDashboard extends Component {
76 searchRecipes, 139 searchRecipes,
77 resetSearch, 140 resetSearch,
78 serviceStatus, 141 serviceStatus,
79 devRecipesCount,
80 searchNeedle, 142 searchNeedle,
143 recipeFilter,
144 recipeDirectory,
145 openRecipeDirectory,
146 openDevDocs,
147 classes,
148 isCommunityRecipesIncludedInCurrentPlan,
81 } = this.props; 149 } = this.props;
82 const { intl } = this.context; 150 const { intl } = this.context;
83 151
152
153 const communityRecipes = recipes.filter(r => !r.isDevRecipe);
154 const devRecipes = recipes.filter(r => r.isDevRecipe);
155
84 return ( 156 return (
85 <div className="settings__main"> 157 <div className="settings__main">
86 <div className="settings__header"> 158 <div className="settings__header">
87 <h1>{intl.formatMessage(messages.headline)}</h1> 159 <h1>{intl.formatMessage(messages.headline)}</h1>
88 </div> 160 </div>
161 <LimitReachedInfobox />
89 <div className="settings__body recipes"> 162 <div className="settings__body recipes">
90 {serviceStatus.length > 0 && serviceStatus.includes('created') && ( 163 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
91 <Appear> 164 <Appear>
@@ -122,20 +195,14 @@ export default @observer class RecipesDashboard extends Component {
122 > 195 >
123 {intl.formatMessage(messages.allRecipes)} 196 {intl.formatMessage(messages.allRecipes)}
124 </Link> 197 </Link>
125 {devRecipesCount > 0 && ( 198 <Link
126 <Link 199 to="/settings/recipes/dev"
127 to="/settings/recipes/dev" 200 className="badge"
128 className="badge" 201 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
129 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} 202 onClick={() => resetSearch()}
130 onClick={() => resetSearch()} 203 >
131 > 204 {intl.formatMessage(messages.customRecipes)}
132 {intl.formatMessage(messages.devRecipes)} 205 </Link>
133 {' '}
134(
135 {devRecipesCount}
136)
137 </Link>
138 )}
139 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> 206 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request">
140 {intl.formatMessage(messages.missingService)} 207 {intl.formatMessage(messages.missingService)}
141 {' '} 208 {' '}
@@ -146,23 +213,78 @@ export default @observer class RecipesDashboard extends Component {
146 {isLoading ? ( 213 {isLoading ? (
147 <Loader /> 214 <Loader />
148 ) : ( 215 ) : (
149 <div className="recipes__list"> 216 <>
150 {hasLoadedRecipes && recipes.length === 0 && ( 217 {recipeFilter === 'dev' && (
151 <p className="align-middle settings__empty-state"> 218 <>
152 <span className="emoji"> 219 <H2>
153 <img src="./assets/images/emoji/dontknow.png" alt="" /> 220 {intl.formatMessage(messages.headlineCustomRecipes)}
154 </span> 221 {isCommunityRecipesIncludedInCurrentPlan && (
155 {intl.formatMessage(messages.nothingFound)} 222 <ProBadge className={classes.proBadge} />
156 </p> 223 )}
224 </H2>
225 <div className={classes.devRecipeIntroContainer}>
226 <p>
227 {intl.formatMessage(messages.customRecipeIntro)}
228 </p>
229 <Input
230 value={recipeDirectory}
231 className={classes.path}
232 showLabel={false}
233 />
234 <div className={classes.actionContainer}>
235 <Button
236 onClick={openRecipeDirectory}
237 buttonType="secondary"
238 label={intl.formatMessage(messages.openFolder)}
239 />
240 <Button
241 onClick={openDevDocs}
242 buttonType="secondary"
243 label={intl.formatMessage(messages.openDevDocs)}
244 />
245 </div>
246 </div>
247 </>
248 )}
249 <PremiumFeatureContainer
250 condition={(recipeFilter === 'dev' && communityRecipes.length > 0) && isCommunityRecipesIncludedInCurrentPlan}
251 >
252 {recipeFilter === 'dev' && communityRecipes.length > 0 && (
253 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3>
254 )}
255 <div className="recipes__list">
256 {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && (
257 <p className="align-middle settings__empty-state">
258 <span className="emoji">
259 <img src="./assets/images/emoji/dontknow.png" alt="" />
260 </span>
261 {intl.formatMessage(messages.nothingFound)}
262 </p>
263 )}
264 {communityRecipes.map(recipe => (
265 <RecipeItem
266 key={recipe.id}
267 recipe={recipe}
268 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
269 />
270 ))}
271 </div>
272 </PremiumFeatureContainer>
273 {recipeFilter === 'dev' && devRecipes.length > 0 && (
274 <div className={classes.devRecipeList}>
275 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3>
276 <div className="recipes__list">
277 {devRecipes.map(recipe => (
278 <RecipeItem
279 key={recipe.id}
280 recipe={recipe}
281 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
282 />
283 ))}
284 </div>
285 </div>
157 )} 286 )}
158 {recipes.map(recipe => ( 287 </>
159 <RecipeItem
160 key={recipe.id}
161 recipe={recipe}
162 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
163 />
164 ))}
165 </div>
166 )} 288 )}
167 </div> 289 </div>
168 </div> 290 </div>
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 4ba2eb844..5cde0db8e 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -17,6 +17,8 @@ import ImageUpload from '../../ui/ImageUpload';
17import Select from '../../ui/Select'; 17import Select from '../../ui/Select';
18 18
19import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; 19import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
20import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
21import { serviceLimitStore } from '../../../features/serviceLimit';
20 22
21const messages = defineMessages({ 23const messages = defineMessages({
22 saveService: { 24 saveService: {
@@ -128,8 +130,8 @@ export default @observer class EditServiceForm extends Component {
128 isSaving: PropTypes.bool.isRequired, 130 isSaving: PropTypes.bool.isRequired,
129 isDeleting: PropTypes.bool.isRequired, 131 isDeleting: PropTypes.bool.isRequired,
130 isProxyFeatureEnabled: PropTypes.bool.isRequired, 132 isProxyFeatureEnabled: PropTypes.bool.isRequired,
131 isProxyPremiumFeature: PropTypes.bool.isRequired, 133 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired,
132 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, 134 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
133 }; 135 };
134 136
135 static defaultProps = { 137 static defaultProps = {
@@ -192,8 +194,8 @@ export default @observer class EditServiceForm extends Component {
192 isDeleting, 194 isDeleting,
193 onDelete, 195 onDelete,
194 isProxyFeatureEnabled, 196 isProxyFeatureEnabled,
195 isProxyPremiumFeature, 197 isServiceProxyIncludedInCurrentPlan,
196 isSpellcheckerPremiumFeature, 198 isSpellcheckerIncludedInCurrentPlan,
197 } = this.props; 199 } = this.props;
198 const { intl } = this.context; 200 const { intl } = this.context;
199 201
@@ -252,6 +254,7 @@ export default @observer class EditServiceForm extends Component {
252 )} 254 )}
253 </span> 255 </span>
254 </div> 256 </div>
257 <LimitReachedInfobox />
255 <div className="settings__body"> 258 <div className="settings__body">
256 <form onSubmit={e => this.submit(e)} id="form"> 259 <form onSubmit={e => this.submit(e)} id="form">
257 <div className="service-name"> 260 <div className="service-name">
@@ -342,7 +345,7 @@ export default @observer class EditServiceForm extends Component {
342 </div> 345 </div>
343 346
344 <PremiumFeatureContainer 347 <PremiumFeatureContainer
345 condition={isSpellcheckerPremiumFeature} 348 condition={!isSpellcheckerIncludedInCurrentPlan}
346 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 349 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
347 > 350 >
348 <div className="settings__settings-group"> 351 <div className="settings__settings-group">
@@ -352,7 +355,7 @@ export default @observer class EditServiceForm extends Component {
352 355
353 {isProxyFeatureEnabled && ( 356 {isProxyFeatureEnabled && (
354 <PremiumFeatureContainer 357 <PremiumFeatureContainer
355 condition={isProxyPremiumFeature} 358 condition={!isServiceProxyIncludedInCurrentPlan}
356 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }} 359 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }}
357 > 360 >
358 <div className="settings__settings-group"> 361 <div className="settings__settings-group">
@@ -418,7 +421,7 @@ export default @observer class EditServiceForm extends Component {
418 type="submit" 421 type="submit"
419 label={intl.formatMessage(messages.saveService)} 422 label={intl.formatMessage(messages.saveService)}
420 htmlForm="form" 423 htmlForm="form"
421 disabled={action !== 'edit' && form.isPristine && requiresUserInput} 424 disabled={action !== 'edit' && ((form.isPristine && requiresUserInput) || serviceLimitStore.userHasReachedServiceLimit)}
422 /> 425 />
423 )} 426 )}
424 </div> 427 </div>
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index 53bae12df..78038e86a 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -9,6 +9,7 @@ import Infobox from '../../ui/Infobox';
9import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
10import ServiceItem from './ServiceItem'; 10import ServiceItem from './ServiceItem';
11import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
12import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 headline: { 15 headline: {
@@ -91,6 +92,7 @@ export default @observer class ServicesDashboard extends Component {
91 <div className="settings__header"> 92 <div className="settings__header">
92 <h1>{intl.formatMessage(messages.headline)}</h1> 93 <h1>{intl.formatMessage(messages.headline)}</h1>
93 </div> 94 </div>
95 <LimitReachedInfobox />
94 <div className="settings__body"> 96 <div className="settings__body">
95 {!isLoading && ( 97 {!isLoading && (
96 <SearchInput 98 <SearchInput
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index efd453356..3f9e0a6bc 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -100,7 +100,7 @@ export default @observer class EditSettingsForm extends Component {
100 isClearingAllCache: PropTypes.bool.isRequired, 100 isClearingAllCache: PropTypes.bool.isRequired,
101 onClearAllCache: PropTypes.func.isRequired, 101 onClearAllCache: PropTypes.func.isRequired,
102 cacheSize: PropTypes.string.isRequired, 102 cacheSize: PropTypes.string.isRequired,
103 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, 103 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
104 }; 104 };
105 105
106 static contextTypes = { 106 static contextTypes = {
@@ -130,7 +130,7 @@ export default @observer class EditSettingsForm extends Component {
130 isClearingAllCache, 130 isClearingAllCache,
131 onClearAllCache, 131 onClearAllCache,
132 cacheSize, 132 cacheSize,
133 isSpellcheckerPremiumFeature, 133 isSpellcheckerIncludedInCurrentPlan,
134 } = this.props; 134 } = this.props;
135 const { intl } = this.context; 135 const { intl } = this.context;
136 136
@@ -173,7 +173,7 @@ export default @observer class EditSettingsForm extends Component {
173 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> 173 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2>
174 <Select field={form.$('locale')} showLabel={false} /> 174 <Select field={form.$('locale')} showLabel={false} />
175 <PremiumFeatureContainer 175 <PremiumFeatureContainer
176 condition={isSpellcheckerPremiumFeature} 176 condition={!isSpellcheckerIncludedInCurrentPlan}
177 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 177 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
178 > 178 >
179 <Fragment> 179 <Fragment>
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 82c517fcb..990ee52e7 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -133,13 +133,13 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
133 </div> 133 </div>
134 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" /> 134 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" />
135 </div> 135 </div>
136 <Button
137 label={intl.formatMessage(messages.manageButton)}
138 onClick={openTeamManagement}
139 className={classes.cta}
140 />
141 </> 136 </>
142 </PremiumFeatureContainer> 137 </PremiumFeatureContainer>
138 <Button
139 label={intl.formatMessage(messages.manageButton)}
140 onClick={openTeamManagement}
141 className={classes.cta}
142 />
143 </> 143 </>
144 )} 144 )}
145 </> 145 </>
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
index 50f1e0522..cdfbbe60d 100644
--- a/src/components/subscription/SubscriptionForm.js
+++ b/src/components/subscription/SubscriptionForm.js
@@ -1,214 +1,78 @@
1import React, { Component, Fragment } 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 } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
5 6
6import Form from '../../lib/Form'; 7import { H3, H2 } from '@meetfranz/ui';
7import Radio from '../ui/Radio';
8import Button from '../ui/Button';
9import Loader from '../ui/Loader';
10 8
11import { required } from '../../helpers/validation-helpers'; 9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
12 11
13const messages = defineMessages({ 12const messages = defineMessages({
14 submitButtonLabel: { 13 submitButtonLabel: {
15 id: 'subscription.submit.label', 14 id: 'subscription.cta.choosePlan',
16 defaultMessage: '!!!Support the development of Franz', 15 defaultMessage: '!!!Choose your plan',
17 }, 16 },
18 paymentSessionError: { 17 teaserHeadline: {
19 id: 'subscription.paymentSessionError', 18 id: 'settings.account.headlineUpgradeAccount',
20 defaultMessage: '!!!Could not initialize payment form', 19 defaultMessage: '!!!Upgrade your account and get the full Franz experience',
21 }, 20 },
22 typeFree: { 21 teaserText: {
23 id: 'subscription.type.free', 22 id: 'subscription.teaser.intro',
24 defaultMessage: '!!!free', 23 defaultMessage: '!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!',
25 },
26 typeMonthly: {
27 id: 'subscription.type.month',
28 defaultMessage: '!!!month',
29 },
30 typeYearly: {
31 id: 'subscription.type.year',
32 defaultMessage: '!!!year',
33 }, 24 },
34 includedFeatures: { 25 includedFeatures: {
35 id: 'subscription.includedFeatures', 26 id: 'subscription.teaser.includedFeatures',
36 defaultMessage: '!!!The Franz Premium Supporter Account includes', 27 defaultMessage: '!!!Paid Franz Plans include:',
37 },
38 onpremise: {
39 id: 'subscription.features.onpremise.mattermost',
40 defaultMessage: '!!!Add on-premise/hosted services like Mattermost',
41 },
42 noInterruptions: {
43 id: 'subscription.features.noInterruptions',
44 defaultMessage: '!!!No app delays & nagging to upgrade license',
45 },
46 proxy: {
47 id: 'subscription.features.proxy',
48 defaultMessage: '!!!Proxy support for services',
49 },
50 spellchecker: {
51 id: 'subscription.features.spellchecker',
52 defaultMessage: '!!!Support for Spellchecker',
53 }, 28 },
54 workspaces: { 29});
55 id: 'subscription.features.workspaces', 30
56 defaultMessage: '!!!Organize your services in workspaces', 31const styles = () => ({
57 }, 32 activateTrialButton: {
58 ads: { 33 margin: [40, 0, 50],
59 id: 'subscription.features.ads',
60 defaultMessage: '!!!No ads, ever!',
61 },
62 comingSoon: {
63 id: 'subscription.features.comingSoon',
64 defaultMessage: '!!!coming soon',
65 },
66 euTaxInfo: {
67 id: 'subscription.euTaxInfo',
68 defaultMessage: '!!!EU residents: local sales tax may apply',
69 }, 34 },
70}); 35});
71 36
72export default @observer class SubscriptionForm extends Component { 37export default @observer @injectSheet(styles) class SubscriptionForm extends Component {
73 static propTypes = { 38 static propTypes = {
74 plan: MobxPropTypes.objectOrObservableObject.isRequired, 39 selectPlan: PropTypes.func.isRequired,
75 isLoading: PropTypes.bool.isRequired, 40 isActivatingTrial: PropTypes.bool.isRequired,
76 handlePayment: PropTypes.func.isRequired, 41 classes: PropTypes.object.isRequired,
77 retryPlanRequest: PropTypes.func.isRequired,
78 isCreatingHostedPage: PropTypes.bool.isRequired,
79 error: PropTypes.bool.isRequired,
80 showSkipOption: PropTypes.bool,
81 skipAction: PropTypes.func,
82 skipButtonLabel: PropTypes.string,
83 hideInfo: PropTypes.bool.isRequired,
84 };
85
86 static defaultProps = {
87 showSkipOption: false,
88 skipAction: () => null,
89 skipButtonLabel: '',
90 }; 42 };
91 43
92 static contextTypes = { 44 static contextTypes = {
93 intl: intlShape, 45 intl: intlShape,
94 }; 46 };
95 47
96 componentWillMount() {
97 this.form = this.prepareForm();
98 }
99
100 prepareForm() {
101 const { intl } = this.context;
102
103 const form = {
104 fields: {
105 paymentTier: {
106 value: 'year',
107 validators: [required],
108 options: [{
109 value: 'month',
110 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month')
111 ? `${this.props.plan.month.price} / ${intl.formatMessage(messages.typeMonthly)}`
112 : 'monthly'}`,
113 }, {
114 value: 'year',
115 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year')
116 ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}`
117 : 'yearly'}`,
118 }],
119 },
120 },
121 };
122
123 if (this.props.showSkipOption) {
124 form.fields.paymentTier.options.unshift({
125 value: 'skip',
126 label: `€ 0 / ${intl.formatMessage(messages.typeFree)}`,
127 });
128 }
129
130 return new Form(form, this.context.intl);
131 }
132
133 render() { 48 render() {
134 const { 49 const {
135 isLoading, 50 isActivatingTrial,
136 isCreatingHostedPage, 51 selectPlan,
137 handlePayment, 52 classes,
138 retryPlanRequest,
139 error,
140 showSkipOption,
141 skipAction,
142 skipButtonLabel,
143 hideInfo,
144 } = this.props; 53 } = this.props;
145 const { intl } = this.context; 54 const { intl } = this.context;
146 55
147 if (error) { 56 return (
148 return ( 57 <>
58 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
59 <p>{intl.formatMessage(messages.teaserText)}</p>
149 <Button 60 <Button
150 label="Reload" 61 label={intl.formatMessage(messages.submitButtonLabel)}
151 onClick={retryPlanRequest} 62 className={classes.activateTrialButton}
152 isLoaded={!isLoading} 63 busy={isActivatingTrial}
64 onClick={selectPlan}
65 stretch
153 /> 66 />
154 ); 67 <div className="subscription__premium-info">
155 } 68 <H3>
156 69 {intl.formatMessage(messages.includedFeatures)}
157 return ( 70 </H3>
158 <Loader loaded={!isLoading}> 71 <div className="subscription">
159 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> 72 <FeatureList />
160 {!hideInfo && (
161 <div className="subscription__premium-info">
162 <p>
163 <strong>{intl.formatMessage(messages.includedFeatures)}</strong>
164 </p>
165 <div className="subscription">
166 <ul className="subscription__premium-features">
167 <li>{intl.formatMessage(messages.onpremise)}</li>
168 <li>
169 {intl.formatMessage(messages.noInterruptions)}
170 </li>
171 <li>
172 {intl.formatMessage(messages.spellchecker)}
173 </li>
174 <li>
175 {intl.formatMessage(messages.proxy)}
176 </li>
177 <li>
178 {intl.formatMessage(messages.workspaces)}
179 </li>
180 <li>
181 {intl.formatMessage(messages.ads)}
182 </li>
183 </ul>
184 </div>
185 </div> 73 </div>
186 )} 74 </div>
187 <Fragment> 75 </>
188 {error.code === 'no-payment-session' && (
189 <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p>
190 )}
191 </Fragment>
192 {showSkipOption && this.form.$('paymentTier').value === 'skip' ? (
193 <Button
194 label={skipButtonLabel}
195 className="auth__button"
196 onClick={skipAction}
197 />
198 ) : (
199 <Button
200 label={intl.formatMessage(messages.submitButtonLabel)}
201 className="auth__button"
202 loaded={!isCreatingHostedPage}
203 onClick={() => handlePayment(this.form.$('paymentTier').value)}
204 />
205 )}
206 {this.form.$('paymentTier').value !== 'skip' && (
207 <p className="legal">
208 {intl.formatMessage(messages.euTaxInfo)}
209 </p>
210 )}
211 </Loader>
212 ); 76 );
213 } 77 }
214} 78}
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js
index 0f6f0260f..12ef8a6e9 100644
--- a/src/components/subscription/SubscriptionPopup.js
+++ b/src/components/subscription/SubscriptionPopup.js
@@ -43,7 +43,7 @@ export default @observer class SubscriptionPopup extends Component {
43 43
44 setTimeout(() => { 44 setTimeout(() => {
45 this.props.closeWindow(); 45 this.props.closeWindow();
46 }, ms('4s')); 46 }, ms('1s'));
47 } 47 }
48 48
49 render() { 49 render() {
@@ -61,8 +61,6 @@ export default @observer class SubscriptionPopup extends Component {
61 autosize 61 autosize
62 src={encodeURI(url)} 62 src={encodeURI(url)}
63 onDidNavigate={completeCheck} 63 onDidNavigate={completeCheck}
64 // onNewWindow={(event, url, frameName, options) =>
65 // openWindow({ event, url, frameName, options })}
66 /> 64 />
67 </div> 65 </div>
68 <div className="subscription-popup__toolbar franz-form"> 66 <div className="subscription-popup__toolbar franz-form">
diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js
new file mode 100644
index 000000000..9fe1c93b7
--- /dev/null
+++ b/src/components/subscription/TrialForm.js
@@ -0,0 +1,114 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import { H3, H2 } from '@meetfranz/ui';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
11import { FeatureItem } from '../ui/FeatureItem';
12
13const messages = defineMessages({
14 submitButtonLabel: {
15 id: 'subscription.cta.activateTrial',
16 defaultMessage: '!!!Yes, start the free Franz Professional trial',
17 },
18 allOptionsButton: {
19 id: 'subscription.cta.allOptions',
20 defaultMessage: '!!!See all options',
21 },
22 teaserHeadline: {
23 id: 'settings.account.headlineTrialUpgrade',
24 defaultMessage: '!!!Get the free 14 day Franz Professional Trial',
25 },
26 includedFeatures: {
27 id: 'subscription.includedProFeatures',
28 defaultMessage: '!!!The Franz Professional Plan includes:',
29 },
30 noStringsAttachedHeadline: {
31 id: 'pricing.trial.terms.headline',
32 defaultMessage: '!!!No strings attached',
33 },
34 noCreditCard: {
35 id: 'pricing.trial.terms.noCreditCard',
36 defaultMessage: '!!!No credit card required',
37 },
38 automaticTrialEnd: {
39 id: 'pricing.trial.terms.automaticTrialEnd',
40 defaultMessage: '!!!Your free trial ends automatically after 14 days',
41 },
42});
43
44const styles = () => ({
45 activateTrialButton: {
46 margin: [40, 0, 10],
47 },
48 allOptionsButton: {
49 margin: [0, 0, 40],
50 background: 'none',
51 border: 'none',
52 },
53 keyTerms: {
54 marginTop: 20,
55 },
56});
57
58export default @observer @injectSheet(styles) class TrialForm extends Component {
59 static propTypes = {
60 activateTrial: PropTypes.func.isRequired,
61 isActivatingTrial: PropTypes.bool.isRequired,
62 showAllOptions: PropTypes.func.isRequired,
63 classes: PropTypes.object.isRequired,
64 };
65
66 static contextTypes = {
67 intl: intlShape,
68 };
69
70 render() {
71 const {
72 isActivatingTrial,
73 activateTrial,
74 showAllOptions,
75 classes,
76 } = this.props;
77 const { intl } = this.context;
78
79 return (
80 <>
81 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
82 <H3 className={classes.keyTerms}>
83 {intl.formatMessage(messages.noStringsAttachedHeadline)}
84 </H3>
85 <ul>
86 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
87 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
88 </ul>
89
90 <Button
91 label={intl.formatMessage(messages.submitButtonLabel)}
92 className={classes.activateTrialButton}
93 busy={isActivatingTrial}
94 onClick={activateTrial}
95 stretch
96 />
97 <Button
98 label={intl.formatMessage(messages.allOptionsButton)}
99 className={classes.allOptionsButton}
100 onClick={showAllOptions}
101 stretch
102 />
103 <div className="subscription__premium-info">
104 <H3>
105 {intl.formatMessage(messages.includedFeatures)}
106 </H3>
107 <div className="subscription">
108 <FeatureList />
109 </div>
110 </div>
111 </>
112 );
113 }
114}
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
new file mode 100644
index 000000000..53616f2eb
--- /dev/null
+++ b/src/components/ui/FeatureItem.js
@@ -0,0 +1,37 @@
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.legacyStyles.themeGrayDark],
10 padding: [8, 0],
11 display: 'flex',
12 alignItems: 'center',
13 },
14 featureIcon: {
15 fill: theme.brandSuccess,
16 marginRight: 10,
17 },
18});
19
20export const FeatureItem = injectSheet(styles)(({
21 classes, className, name, icon,
22}) => (
23 <li className={classnames({
24 [classes.featureItem]: true,
25 [className]: className,
26 })}
27 >
28 {icon ? (
29 <span className={classes.featureIcon}>{icon}</span>
30 ) : (
31 <Icon icon={mdiCheckCircle} className={classes.featureIcon} size={1.5} />
32 )}
33 {name}
34 </li>
35));
36
37export default FeatureItem;
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
new file mode 100644
index 000000000..62944ad75
--- /dev/null
+++ b/src/components/ui/FeatureList.js
@@ -0,0 +1,89 @@
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 unlimitedServices: {
9 id: 'pricing.features.unlimitedServices',
10 defaultMessage: '!!!Add unlimited services',
11 },
12 spellchecker: {
13 id: 'pricing.features.spellchecker',
14 defaultMessage: '!!!Spellchecker support',
15 },
16 workspaces: {
17 id: 'pricing.features.workspaces',
18 defaultMessage: '!!!Workspaces',
19 },
20 customWebsites: {
21 id: 'pricing.features.customWebsites',
22 defaultMessage: '!!!Add Custom Websites',
23 },
24 onPremise: {
25 id: 'pricing.features.onPremise',
26 defaultMessage: '!!!On-premise & other Hosted Services',
27 },
28 thirdPartyServices: {
29 id: 'pricing.features.thirdPartyServices',
30 defaultMessage: '!!!Install 3rd party services',
31 },
32 serviceProxies: {
33 id: 'pricing.features.serviceProxies',
34 defaultMessage: '!!!Service Proxies',
35 },
36 teamManagement: {
37 id: 'pricing.features.teamManagement',
38 defaultMessage: '!!!Team Management',
39 },
40 appDelays: {
41 id: 'pricing.features.appDelays',
42 defaultMessage: '!!!No Waiting Screens',
43 },
44 adFree: {
45 id: 'pricing.features.adFree',
46 defaultMessage: '!!!Forever ad-free',
47 },
48});
49
50export class FeatureList extends Component {
51 static propTypes = {
52 className: PropTypes.string,
53 featureClassName: PropTypes.string,
54 };
55
56 static defaultProps = {
57 className: '',
58 featureClassName: '',
59 }
60
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 render() {
66 const {
67 className,
68 featureClassName,
69 } = this.props;
70 const { intl } = this.context;
71
72 return (
73 <ul className={className}>
74 <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} className={featureClassName} />
75 <FeatureItem name={intl.formatMessage(messages.spellchecker)} className={featureClassName} />
76 <FeatureItem name={intl.formatMessage(messages.workspaces)} className={featureClassName} />
77 <FeatureItem name={intl.formatMessage(messages.customWebsites)} className={featureClassName} />
78 <FeatureItem name={intl.formatMessage(messages.onPremise)} className={featureClassName} />
79 <FeatureItem name={intl.formatMessage(messages.thirdPartyServices)} className={featureClassName} />
80 <FeatureItem name={intl.formatMessage(messages.serviceProxies)} className={featureClassName} />
81 <FeatureItem name={intl.formatMessage(messages.teamManagement)} className={featureClassName} />
82 <FeatureItem name={intl.formatMessage(messages.appDelays)} className={featureClassName} />
83 <FeatureItem name={intl.formatMessage(messages.adFree)} className={featureClassName} />
84 </ul>
85 );
86 }
87}
88
89export default FeatureList;
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index 0b7154760..63d858c47 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -5,6 +5,7 @@ import classnames from 'classnames';
5import injectCSS from 'react-jss'; 5import injectCSS from 'react-jss';
6import { Icon } from '@meetfranz/ui'; 6import { Icon } from '@meetfranz/ui';
7 7
8import { mdiClose } from '@mdi/js';
8import styles from './styles'; 9import styles from './styles';
9 10
10// ReactModal.setAppElement('#root'); 11// ReactModal.setAppElement('#root');
@@ -59,7 +60,7 @@ export default @injectCSS(styles) class Modal extends Component {
59 className={classes.close} 60 className={classes.close}
60 onClick={close} 61 onClick={close}
61 > 62 >
62 <Icon icon="mdiClose" size={1.5} /> 63 <Icon icon={mdiClose} size={1.5} />
63 </button> 64 </button>
64 )} 65 )}
65 <div className={classes.content}> 66 <div className={classes.content}>
diff --git a/src/config.js b/src/config.js
index 5bc318545..405cc5253 100644
--- a/src/config.js
+++ b/src/config.js
@@ -19,11 +19,16 @@ export const DEV_WS_API = 'wss://dev.franzinfra.com';
19export const LIVE_WS_API = 'wss://api.franzinfra.com'; 19export const LIVE_WS_API = 'wss://api.franzinfra.com';
20 20
21export const LOCAL_API_WEBSITE = 'http://localhost:3333'; 21export const LOCAL_API_WEBSITE = 'http://localhost:3333';
22export const DEV_API_WEBSITE = 'https://meetfranz.com'; 22// export const DEV_API_WEBSITE = 'https://meetfranz.com';t
23export const DEV_API_WEBSITE = 'http://hash-3ac3ccd2472269cf585c58a4f6973d86f3c9e7bd.franzstaging.com/'; // TODO: revert me
23export const LIVE_API_WEBSITE = 'https://meetfranz.com'; 24export const LIVE_API_WEBSITE = 'https://meetfranz.com';
24 25
25export const STATS_API = 'https://stats.franzinfra.com'; 26export const STATS_API = 'https://stats.franzinfra.com';
26 27
28export const LOCAL_TODOS_FRONTEND_URL = 'http://localhost:4000';
29export const PRODUCTION_TODOS_FRONTEND_URL = 'https://app.franztodos.com';
30export const DEVELOPMENT_TODOS_FRONTEND_URL = 'https://development--franz-todos.netlify.com';
31
27export const GA_ID = !isDevMode ? 'UA-74126766-10' : 'UA-74126766-12'; 32export const GA_ID = !isDevMode ? 'UA-74126766-10' : 'UA-74126766-12';
28 33
29export const DEFAULT_APP_SETTINGS = { 34export const DEFAULT_APP_SETTINGS = {
@@ -45,16 +50,16 @@ export const DEFAULT_APP_SETTINGS = {
45}; 50};
46 51
47export const DEFAULT_FEATURES_CONFIG = { 52export const DEFAULT_FEATURES_CONFIG = {
48 isSpellcheckerPremiumFeature: false, 53 isSpellcheckerIncludedInCurrentPlan: true,
49 needToWaitToProceed: false, 54 needToWaitToProceed: false,
50 needToWaitToProceedConfig: { 55 needToWaitToProceedConfig: {
51 delayOffset: ms('1h'), 56 delayOffset: ms('1h'),
52 wait: ms('10s'), 57 wait: ms('10s'),
53 }, 58 },
54 isServiceProxyEnabled: false, 59 isServiceProxyEnabled: false,
55 isServiceProxyPremiumFeature: true, 60 isServiceProxyIncludedInCurrentPlan: false,
56 isAnnouncementsEnabled: true, 61 isAnnouncementsEnabled: true,
57 isWorkspacePremiumFeature: true, 62 isWorkspaceIncludedInCurrentPlan: true,
58 isWorkspaceEnabled: false, 63 isWorkspaceEnabled: false,
59}; 64};
60 65
@@ -67,6 +72,7 @@ export const DEFAULT_WINDOW_OPTIONS = {
67 72
68export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs'; 73export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs';
69export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; 74export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate';
75export const FRANZ_DEV_DOCS = 'http://bit.ly/franz-dev-hub';
70 76
71export const FILE_SYSTEM_SETTINGS_TYPES = [ 77export const FILE_SYSTEM_SETTINGS_TYPES = [
72 'app', 78 'app',
@@ -83,3 +89,25 @@ export const ALLOWED_PROTOCOLS = [
83 'http:', 89 'http:',
84 'ftp:', 90 'ftp:',
85]; 91];
92
93export const PLANS = {
94 PERSONAL: 'PERSONAL_MONTHLY',
95 PRO: 'PRO_MONTHLY',
96 LEGACY: 'LEGACY',
97 FREE: 'FREE',
98};
99
100export const PLANS_MAPPING = {
101 'franz-personal-monthly': PLANS.PERSONAL,
102 'franz-personal-yearly': PLANS.PERSONAL,
103 'franz-pro-monthly': PLANS.PRO,
104 'franz-pro-yearly': PLANS.PRO,
105 'franz-supporter-license': PLANS.LEGACY,
106 'franz-supporter-license-x1': PLANS.LEGACY,
107 'franz-supporter-license-x2': PLANS.LEGACY,
108 'franz-supporter-license-year': PLANS.LEGACY,
109 'franz-supporter-license-year-x1': PLANS.LEGACY,
110 'franz-supporter-license-year-x2': PLANS.LEGACY,
111 'franz-supporter-license-year-2019': PLANS.LEGACY,
112 free: PLANS.FREE,
113};
diff --git a/src/containers/auth/AuthLayoutContainer.js b/src/containers/auth/AuthLayoutContainer.js
index 1f9c1ea61..427054d3d 100644
--- a/src/containers/auth/AuthLayoutContainer.js
+++ b/src/containers/auth/AuthLayoutContainer.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4import { ThemeProvider } from 'react-jss'; 4import { ThemeProvider } from 'react-jss';
5import { theme } from '@meetfranz/theme';
6 5
7import AuthLayout from '../../components/auth/AuthLayout'; 6import AuthLayout from '../../components/auth/AuthLayout';
8import AppStore from '../../stores/AppStore'; 7import AppStore from '../../stores/AppStore';
@@ -24,24 +23,22 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
24 stores, actions, children, location, 23 stores, actions, children, location,
25 } = this.props; 24 } = this.props;
26 const { 25 const {
27 app, features, globalError, settings, 26 app, features, globalError,
28 } = stores; 27 } = stores;
29 28
30 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting 29 const isLoadingBaseFeatures = features.defaultFeaturesRequest.isExecuting
31 && !features.defaultFeaturesRequest.wasExecuted; 30 && !features.defaultFeaturesRequest.wasExecuted;
32 31
33 const themeType = theme(settings.app.darkMode ? 'dark' : 'default');
34
35 if (isLoadingBaseFeatures) { 32 if (isLoadingBaseFeatures) {
36 return ( 33 return (
37 <ThemeProvider theme={theme(themeType)}> 34 <ThemeProvider theme={stores.ui.theme}>
38 <AppLoader /> 35 <AppLoader />
39 </ThemeProvider> 36 </ThemeProvider>
40 ); 37 );
41 } 38 }
42 39
43 return ( 40 return (
44 <ThemeProvider theme={theme(themeType)}> 41 <ThemeProvider theme={stores.ui.theme}>
45 <AuthLayout 42 <AuthLayout
46 error={globalError.response} 43 error={globalError.response}
47 pathname={location.pathname} 44 pathname={location.pathname}
@@ -50,7 +47,6 @@ export default @inject('stores', 'actions') @observer class AuthLayoutContainer
50 retryHealthCheck={actions.app.healthCheck} 47 retryHealthCheck={actions.app.healthCheck}
51 isHealthCheckLoading={app.healthCheckRequest.isExecuting} 48 isHealthCheckLoading={app.healthCheckRequest.isExecuting}
52 isFullScreen={app.isFullScreen} 49 isFullScreen={app.isFullScreen}
53 darkMode={app.isSystemDarkModeEnabled}
54 installAppUpdate={actions.app.installUpdate} 50 installAppUpdate={actions.app.installUpdate}
55 nextAppReleaseVersion={app.nextAppReleaseVersion} 51 nextAppReleaseVersion={app.nextAppReleaseVersion}
56 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED} 52 appUpdateIsDownloaded={app.updateStatus === app.updateStatusTypes.DOWNLOADED}
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js
index 8d179a170..af1651931 100644
--- a/src/containers/auth/PricingScreen.js
+++ b/src/containers/auth/PricingScreen.js
@@ -5,7 +5,6 @@ import { RouterStore } from 'mobx-react-router';
5 5
6import Pricing from '../../components/auth/Pricing'; 6import Pricing from '../../components/auth/Pricing';
7import UserStore from '../../stores/UserStore'; 7import UserStore from '../../stores/UserStore';
8import PaymentStore from '../../stores/PaymentStore';
9 8
10import { globalError as globalErrorPropType } from '../../prop-types'; 9import { globalError as globalErrorPropType } from '../../prop-types';
11 10
@@ -14,20 +13,40 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
14 error: globalErrorPropType.isRequired, 13 error: globalErrorPropType.isRequired,
15 }; 14 };
16 15
16 async submit() {
17 const {
18 actions,
19 stores,
20 } = this.props;
21
22 const { activateTrialRequest } = stores.user;
23 const { defaultTrialPlan } = stores.features.features;
24
25 actions.user.activateTrial({ planId: defaultTrialPlan });
26 await activateTrialRequest._promise;
27
28 if (!activateTrialRequest.isError) {
29 stores.router.push('/');
30 stores.user.hasCompletedSignup = true;
31 }
32 }
33
17 render() { 34 render() {
18 const { actions, stores, error } = this.props; 35 const {
36 error,
37 stores,
38 } = this.props;
19 39
20 const nextStepRoute = stores.user.legacyServices.length ? stores.user.importRoute : stores.user.inviteRoute; 40 const { getUserInfoRequest, activateTrialRequest } = stores.user;
41 const { featuresRequest } = stores.features;
21 42
22 return ( 43 return (
23 <Pricing 44 <Pricing
24 donor={stores.user.data.donor || {}} 45 onSubmit={this.submit.bind(this)}
25 onSubmit={actions.user.signup} 46 isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)}
26 onCloseSubscriptionWindow={() => this.props.stores.router.push(nextStepRoute)} 47 isActivatingTrial={activateTrialRequest.isExecuting}
27 isLoading={stores.payment.plansRequest.isExecuting} 48 trialActivationError={activateTrialRequest.isError}
28 isLoadingUser={stores.user.getUserInfoRequest.isExecuting}
29 error={error} 49 error={error}
30 skipAction={() => this.props.stores.router.push(nextStepRoute)}
31 /> 50 />
32 ); 51 );
33 } 52 }
@@ -36,12 +55,11 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
36PricingScreen.wrappedComponent.propTypes = { 55PricingScreen.wrappedComponent.propTypes = {
37 actions: PropTypes.shape({ 56 actions: PropTypes.shape({
38 user: PropTypes.shape({ 57 user: PropTypes.shape({
39 signup: PropTypes.func.isRequired, 58 activateTrial: PropTypes.func.isRequired,
40 }).isRequired, 59 }).isRequired,
41 }).isRequired, 60 }).isRequired,
42 stores: PropTypes.shape({ 61 stores: PropTypes.shape({
43 user: PropTypes.instanceOf(UserStore).isRequired, 62 user: PropTypes.instanceOf(UserStore).isRequired,
44 payment: PropTypes.instanceOf(PaymentStore).isRequired,
45 router: PropTypes.instanceOf(RouterStore).isRequired, 63 router: PropTypes.instanceOf(RouterStore).isRequired,
46 }).isRequired, 64 }).isRequired,
47}; 65};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index d290a6094..a14a98554 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -10,6 +10,7 @@ import FeaturesStore from '../../stores/FeaturesStore';
10import UIStore from '../../stores/UIStore'; 10import UIStore from '../../stores/UIStore';
11import NewsStore from '../../stores/NewsStore'; 11import NewsStore from '../../stores/NewsStore';
12import SettingsStore from '../../stores/SettingsStore'; 12import SettingsStore from '../../stores/SettingsStore';
13import UserStore from '../../stores/UserStore';
13import RequestStore from '../../stores/RequestStore'; 14import RequestStore from '../../stores/RequestStore';
14import GlobalErrorStore from '../../stores/GlobalErrorStore'; 15import GlobalErrorStore from '../../stores/GlobalErrorStore';
15 16
@@ -39,6 +40,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
39 settings, 40 settings,
40 globalError, 41 globalError,
41 requests, 42 requests,
43 user,
42 } = this.props.stores; 44 } = this.props.stores;
43 45
44 const { 46 const {
@@ -125,6 +127,8 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
125 reload={reload} 127 reload={reload}
126 openSettings={openSettings} 128 openSettings={openSettings}
127 update={updateService} 129 update={updateService}
130 userHasCompletedSignup={user.hasCompletedSignup}
131 hasActivatedTrial={user.hasActivatedTrial}
128 /> 132 />
129 ); 133 );
130 134
@@ -148,8 +152,8 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
148 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful} 152 areRequiredRequestsSuccessful={requests.areRequiredRequestsSuccessful}
149 retryRequiredRequests={retryRequiredRequests} 153 retryRequiredRequests={retryRequiredRequests}
150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 154 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
151 darkMode={settings.all.app.darkMode}
152 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} 155 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible}
156 hasActivatedTrial={user.hasActivatedTrial}
153 > 157 >
154 {React.Children.count(children) > 0 ? children : null} 158 {React.Children.count(children) > 0 ? children : null}
155 </AppLayout> 159 </AppLayout>
@@ -167,6 +171,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
167 ui: PropTypes.instanceOf(UIStore).isRequired, 171 ui: PropTypes.instanceOf(UIStore).isRequired,
168 news: PropTypes.instanceOf(NewsStore).isRequired, 172 news: PropTypes.instanceOf(NewsStore).isRequired,
169 settings: PropTypes.instanceOf(SettingsStore).isRequired, 173 settings: PropTypes.instanceOf(SettingsStore).isRequired,
174 user: PropTypes.instanceOf(UserStore).isRequired,
170 requests: PropTypes.instanceOf(RequestStore).isRequired, 175 requests: PropTypes.instanceOf(RequestStore).isRequired,
171 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, 176 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
172 }).isRequired, 177 }).isRequired,
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
index 66076504f..f9eae4957 100644
--- a/src/containers/settings/AccountScreen.js
+++ b/src/containers/settings/AccountScreen.js
@@ -26,7 +26,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
26 handleWebsiteLink(route) { 26 handleWebsiteLink(route) {
27 const { actions, stores } = this.props; 27 const { actions, stores } = this.props;
28 28
29 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`; 29 const url = stores.user.getAuthURL(`${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`);
30 30
31 actions.app.openExternalUrl({ url }); 31 actions.app.openExternalUrl({ url });
32 } 32 }
@@ -51,6 +51,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
51 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} 51 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting}
52 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} 52 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError}
53 openEditAccount={() => this.handleWebsiteLink('/user/profile')} 53 openEditAccount={() => this.handleWebsiteLink('/user/profile')}
54 upgradeToPro={() => this.handleWebsiteLink('/inapp/user/licenses')}
54 openBilling={() => this.handleWebsiteLink('/user/billing')} 55 openBilling={() => this.handleWebsiteLink('/user/billing')}
55 openInvoices={() => this.handleWebsiteLink('/user/invoices')} 56 openInvoices={() => this.handleWebsiteLink('/user/invoices')}
56 /> 57 />
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index 870ca4ecd..e4ff03bb3 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -330,8 +330,8 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
330 onSubmit={d => this.onSubmit(d)} 330 onSubmit={d => this.onSubmit(d)}
331 onDelete={() => this.deleteService()} 331 onDelete={() => this.deleteService()}
332 isProxyFeatureEnabled={proxyFeature.isEnabled} 332 isProxyFeatureEnabled={proxyFeature.isEnabled}
333 isProxyPremiumFeature={proxyFeature.isPremium} 333 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan}
334 isSpellcheckerPremiumFeature={spellcheckerFeature.isPremium} 334 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan}
335 /> 335 />
336 </ErrorBoundary> 336 </ErrorBoundary>
337 ); 337 );
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 97c1fa3b1..67d52f102 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -159,8 +159,8 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
159 }, 159 },
160 enableSpellchecking: { 160 enableSpellchecking: {
161 label: intl.formatMessage(messages.enableSpellchecking), 161 label: intl.formatMessage(messages.enableSpellchecking),
162 value: !this.props.stores.user.data.isPremium && spellcheckerConfig.isPremium ? false : settings.all.app.enableSpellchecking, 162 value: !this.props.stores.user.data.isPremium && !spellcheckerConfig.isIncludedInCurrentPlan ? false : settings.all.app.enableSpellchecking,
163 default: !this.props.stores.user.data.isPremium && spellcheckerConfig.isPremium ? false : DEFAULT_APP_SETTINGS.enableSpellchecking, 163 default: !this.props.stores.user.data.isPremium && !spellcheckerConfig.isIncludedInCurrentPlan ? false : DEFAULT_APP_SETTINGS.enableSpellchecking,
164 }, 164 },
165 spellcheckerLanguage: { 165 spellcheckerLanguage: {
166 label: intl.formatMessage(globalMessages.spellcheckerLanguage), 166 label: intl.formatMessage(globalMessages.spellcheckerLanguage),
@@ -223,7 +223,7 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
223 cacheSize={cacheSize} 223 cacheSize={cacheSize}
224 isClearingAllCache={isClearingAllCache} 224 isClearingAllCache={isClearingAllCache}
225 onClearAllCache={clearAllCache} 225 onClearAllCache={clearAllCache}
226 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremium} 226 isSpellcheckerIncludedInCurrentPlan={spellcheckerConfig.isIncludedInCurrentPlan}
227 /> 227 />
228 </ErrorBoundary> 228 </ErrorBoundary>
229 ); 229 );
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
index eda5ae54c..132820b6f 100644
--- a/src/containers/settings/RecipesScreen.js
+++ b/src/containers/settings/RecipesScreen.js
@@ -1,7 +1,9 @@
1import { remote, shell } from 'electron';
1import React, { Component } from 'react'; 2import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
3import { autorun } from 'mobx'; 4import { autorun } from 'mobx';
4import { inject, observer } from 'mobx-react'; 5import { inject, observer } from 'mobx-react';
6import path from 'path';
5 7
6import RecipePreviewsStore from '../../stores/RecipePreviewsStore'; 8import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import RecipeStore from '../../stores/RecipesStore'; 9import RecipeStore from '../../stores/RecipesStore';
@@ -10,6 +12,11 @@ import UserStore from '../../stores/UserStore';
10 12
11import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; 13import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary'; 14import ErrorBoundary from '../../components/util/ErrorBoundary';
15import { FRANZ_DEV_DOCS } from '../../config';
16import { gaEvent } from '../../lib/analytics';
17import { communityRecipesStore } from '../../features/communityRecipes';
18
19const { app } = remote;
13 20
14export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { 21export default @inject('stores', 'actions') @observer class RecipesScreen extends Component {
15 static propTypes = { 22 static propTypes = {
@@ -67,9 +74,16 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
67 74
68 render() { 75 render() {
69 const { 76 const {
70 recipePreviews, recipes, services, user, 77 recipePreviews,
78 recipes,
79 services,
80 user,
71 } = this.props.stores; 81 } = this.props.stores;
72 const { showAddServiceInterface } = this.props.actions.service; 82
83 const {
84 app: appActions,
85 service: serviceActions,
86 } = this.props.actions;
73 87
74 const { filter } = this.props.params; 88 const { filter } = this.props.params;
75 let recipeFilter; 89 let recipeFilter;
@@ -77,7 +91,7 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
77 if (filter === 'all') { 91 if (filter === 'all') {
78 recipeFilter = recipePreviews.all; 92 recipeFilter = recipePreviews.all;
79 } else if (filter === 'dev') { 93 } else if (filter === 'dev') {
80 recipeFilter = recipePreviews.dev; 94 recipeFilter = communityRecipesStore.communityRecipes;
81 } else { 95 } else {
82 recipeFilter = recipePreviews.featured; 96 recipeFilter = recipePreviews.featured;
83 } 97 }
@@ -89,6 +103,8 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
89 || recipes.installRecipeRequest.isExecuting 103 || recipes.installRecipeRequest.isExecuting
90 || recipePreviews.searchRecipePreviewsRequest.isExecuting; 104 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
91 105
106 const recipeDirectory = path.join(app.getPath('userData'), 'recipes', 'dev');
107
92 return ( 108 return (
93 <ErrorBoundary> 109 <ErrorBoundary>
94 <RecipesDashboard 110 <RecipesDashboard
@@ -97,12 +113,23 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
97 addedServiceCount={services.all.length} 113 addedServiceCount={services.all.length}
98 isPremium={user.data.isPremium} 114 isPremium={user.data.isPremium}
99 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted} 115 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 showAddServiceInterface={showAddServiceInterface} 116 showAddServiceInterface={serviceActions.showAddServiceInterface}
101 searchRecipes={e => this.searchRecipes(e)} 117 searchRecipes={e => this.searchRecipes(e)}
102 resetSearch={() => this.resetSearch()} 118 resetSearch={() => this.resetSearch()}
103 searchNeedle={this.state.needle} 119 searchNeedle={this.state.needle}
104 serviceStatus={services.actionStatus} 120 serviceStatus={services.actionStatus}
105 devRecipesCount={recipePreviews.dev.length} 121 recipeFilter={filter}
122 recipeDirectory={recipeDirectory}
123 openRecipeDirectory={() => {
124 shell.openItem(recipeDirectory);
125 gaEvent('Recipe', 'open-recipe-folder', 'Open Folder');
126 }}
127 openDevDocs={() => {
128 appActions.openExternalUrl({ url: FRANZ_DEV_DOCS });
129 gaEvent('Recipe', 'open-dev-docs', 'Developer Documentation');
130 }}
131 isCommunityRecipesIncludedInCurrentPlan={communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan}
132 isUserPremiumUser={user.isPremium}
106 /> 133 />
107 </ErrorBoundary> 134 </ErrorBoundary>
108 ); 135 );
@@ -117,6 +144,9 @@ RecipesScreen.wrappedComponent.propTypes = {
117 user: PropTypes.instanceOf(UserStore).isRequired, 144 user: PropTypes.instanceOf(UserStore).isRequired,
118 }).isRequired, 145 }).isRequired,
119 actions: PropTypes.shape({ 146 actions: PropTypes.shape({
147 app: PropTypes.shape({
148 openExternalUrl: PropTypes.func.isRequired,
149 }).isRequired,
120 service: PropTypes.shape({ 150 service: PropTypes.shape({
121 showAddServiceInterface: PropTypes.func.isRequired, 151 showAddServiceInterface: PropTypes.func.isRequired,
122 }).isRequired, 152 }).isRequired,
diff --git a/src/containers/settings/SettingsWindow.js b/src/containers/settings/SettingsWindow.js
index 663b9e2e4..440d32a46 100644
--- a/src/containers/settings/SettingsWindow.js
+++ b/src/containers/settings/SettingsWindow.js
@@ -1,4 +1,5 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import ReactDOM from 'react-dom';
2import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
3import { observer, inject } from 'mobx-react'; 4import { observer, inject } from 'mobx-react';
4 5
@@ -10,10 +11,23 @@ import ErrorBoundary from '../../components/util/ErrorBoundary';
10import { workspaceStore } from '../../features/workspaces'; 11import { workspaceStore } from '../../features/workspaces';
11 12
12export default @inject('stores', 'actions') @observer class SettingsContainer extends Component { 13export default @inject('stores', 'actions') @observer class SettingsContainer extends Component {
14 portalRoot = document.querySelector('#portalContainer');
15
16 el = document.createElement('div');
17
18 componentDidMount() {
19 this.portalRoot.appendChild(this.el);
20 }
21
22 componentWillUnmount() {
23 this.portalRoot.removeChild(this.el);
24 }
25
13 render() { 26 render() {
14 const { children, stores } = this.props; 27 const { children, stores } = this.props;
15 const { closeSettings } = this.props.actions.ui; 28 const { closeSettings } = this.props.actions.ui;
16 29
30
17 const navigation = ( 31 const navigation = (
18 <Navigation 32 <Navigation
19 serviceCount={stores.services.all.length} 33 serviceCount={stores.services.all.length}
@@ -21,15 +35,18 @@ export default @inject('stores', 'actions') @observer class SettingsContainer ex
21 /> 35 />
22 ); 36 );
23 37
24 return ( 38 return ReactDOM.createPortal(
25 <ErrorBoundary> 39 (
26 <Layout 40 <ErrorBoundary>
27 navigation={navigation} 41 <Layout
28 closeSettings={closeSettings} 42 navigation={navigation}
29 > 43 closeSettings={closeSettings}
30 {children} 44 >
31 </Layout> 45 {children}
32 </ErrorBoundary> 46 </Layout>
47 </ErrorBoundary>
48 ),
49 this.el,
33 ); 50 );
34 } 51 }
35} 52}
diff --git a/src/containers/settings/TeamScreen.js b/src/containers/settings/TeamScreen.js
index c69d5ad08..b7b1b78cb 100644
--- a/src/containers/settings/TeamScreen.js
+++ b/src/containers/settings/TeamScreen.js
@@ -14,7 +14,6 @@ export default @inject('stores', 'actions') @observer class TeamScreen extends C
14 const { actions, stores } = this.props; 14 const { actions, stores } = this.props;
15 15
16 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`; 16 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`;
17 console.log(url);
18 17
19 actions.app.openExternalUrl({ url }); 18 actions.app.openExternalUrl({ url });
20 } 19 }
diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js
index aa1166f5e..e9e457084 100644
--- a/src/containers/subscription/SubscriptionFormScreen.js
+++ b/src/containers/subscription/SubscriptionFormScreen.js
@@ -1,4 +1,3 @@
1import { remote } from 'electron';
2import React, { Component } from 'react'; 1import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
@@ -6,96 +5,50 @@ import { inject, observer } from 'mobx-react';
6import PaymentStore from '../../stores/PaymentStore'; 5import PaymentStore from '../../stores/PaymentStore';
7 6
8import SubscriptionForm from '../../components/subscription/SubscriptionForm'; 7import SubscriptionForm from '../../components/subscription/SubscriptionForm';
9 8import TrialForm from '../../components/subscription/TrialForm';
10const { BrowserWindow } = remote;
11 9
12export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { 10export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component {
13 static propTypes = { 11 async openBrowser() {
14 onCloseWindow: PropTypes.func,
15 content: PropTypes.node,
16 showSkipOption: PropTypes.bool,
17 skipAction: PropTypes.func,
18 skipButtonLabel: PropTypes.string,
19 hideInfo: PropTypes.bool,
20 }
21
22 static defaultProps = {
23 onCloseWindow: () => null,
24 content: '',
25 showSkipOption: false,
26 skipAction: () => null,
27 skipButtonLabel: '',
28 hideInfo: false,
29 }
30
31 async handlePayment(plan) {
32 const { 12 const {
33 actions, 13 actions,
34 stores, 14 stores,
35 onCloseWindow,
36 } = this.props; 15 } = this.props;
37 16
38 const interval = plan; 17 const {
39 18 user,
40 const { id } = stores.payment.plan[interval]; 19 features,
41 actions.payment.createHostedPage({ 20 } = stores;
42 planId: id,
43 });
44
45 const hostedPage = await stores.payment.createHostedPageRequest;
46 21
47 if (hostedPage.url) { 22 let hostedPageURL = !user.data.hadSubscription ? features.features.planSelectionURL : features.features.subscribeURL;
48 if (hostedPage.legacyCheckoutFlow) { 23 hostedPageURL = user.getAuthURL(hostedPageURL);
49 const paymentWindow = new BrowserWindow({
50 parent: remote.getCurrentWindow(),
51 modal: true,
52 title: '🔒 Franz Supporter License',
53 width: 600,
54 height: window.innerHeight - 100,
55 maxWidth: 600,
56 minWidth: 600,
57 webPreferences: {
58 nodeIntegration: true,
59 webviewTag: true,
60 },
61 });
62 paymentWindow.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`);
63 24
64 paymentWindow.on('closed', () => { 25 actions.app.openExternalUrl({ url: hostedPageURL });
65 onCloseWindow();
66 });
67 } else {
68 actions.app.openExternalUrl({
69 url: hostedPage.url,
70 });
71 }
72 }
73 } 26 }
74 27
75 render() { 28 render() {
76 const { 29 const {
77 content,
78 actions, 30 actions,
79 stores, 31 stores,
80 showSkipOption,
81 skipAction,
82 skipButtonLabel,
83 hideInfo,
84 } = this.props; 32 } = this.props;
33
34 const { data: user } = stores.user;
35
36 if (user.hadSubscription) {
37 return (
38 <SubscriptionForm
39 plan={stores.payment.plan}
40 selectPlan={() => this.openBrowser()}
41 isActivatingTrial={stores.user.activateTrialRequest.isExecuting || stores.user.getUserInfoRequest.isExecuting}
42 />
43 );
44 }
45
85 return ( 46 return (
86 <SubscriptionForm 47 <TrialForm
87 plan={stores.payment.plan} 48 plan={stores.payment.plan}
88 isLoading={stores.payment.plansRequest.isExecuting} 49 activateTrial={() => actions.user.activateTrial({ planId: stores.features.features.defaultTrialPlan })}
89 retryPlanRequest={() => stores.payment.plansRequest.reload()} 50 showAllOptions={() => this.openBrowser()}
90 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting} 51 isActivatingTrial={stores.user.activateTrialRequest.isExecuting || stores.user.getUserInfoRequest.isExecuting}
91 handlePayment={price => this.handlePayment(price)}
92 content={content}
93 error={stores.payment.plansRequest.isError}
94 showSkipOption={showSkipOption}
95 skipAction={skipAction}
96 skipButtonLabel={skipButtonLabel}
97 hideInfo={hideInfo}
98 openExternalUrl={actions.app.openExternalUrl}
99 /> 52 />
100 ); 53 );
101 } 54 }
diff --git a/src/containers/subscription/SubscriptionPopupScreen.js b/src/containers/subscription/SubscriptionPopupScreen.js
index f76d6c5a6..0de5a87c4 100644
--- a/src/containers/subscription/SubscriptionPopupScreen.js
+++ b/src/containers/subscription/SubscriptionPopupScreen.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4 4
5import SubscriptionPopup from '../../components/subscription/SubscriptionPopup'; 5import SubscriptionPopup from '../../components/subscription/SubscriptionPopup';
6import { isDevMode } from '../../environment';
6 7
7 8
8export default @inject('stores', 'actions') @observer class SubscriptionPopupScreen extends Component { 9export default @inject('stores', 'actions') @observer class SubscriptionPopupScreen extends Component {
@@ -13,7 +14,7 @@ export default @inject('stores', 'actions') @observer class SubscriptionPopupScr
13 completeCheck(event) { 14 completeCheck(event) {
14 const { url } = event; 15 const { url } = event;
15 16
16 if ((url.includes('recurly') && url.includes('confirmation')) || (url.includes('meetfranz') && url.includes('success'))) { 17 if ((url.includes('recurly') && url.includes('confirmation')) || ((url.includes('meetfranz') || isDevMode) && url.includes('success'))) {
17 this.setState({ 18 this.setState({
18 complete: true, 19 complete: true,
19 }); 20 });
diff --git a/src/environment.js b/src/environment.js
index ae7a67e4d..707449e09 100644
--- a/src/environment.js
+++ b/src/environment.js
@@ -10,6 +10,9 @@ import {
10 LIVE_WS_API, 10 LIVE_WS_API,
11 LOCAL_WS_API, 11 LOCAL_WS_API,
12 DEV_WS_API, 12 DEV_WS_API,
13 LOCAL_TODOS_FRONTEND_URL,
14 PRODUCTION_TODOS_FRONTEND_URL,
15 DEVELOPMENT_TODOS_FRONTEND_URL,
13} from './config'; 16} from './config';
14 17
15export const isDevMode = isDev; 18export const isDevMode = isDev;
@@ -31,21 +34,26 @@ export const cmdKey = isMac ? 'Cmd' : 'Ctrl';
31let api; 34let api;
32let wsApi; 35let wsApi;
33let web; 36let web;
37let todos;
34if (!isDevMode || (isDevMode && useLiveAPI)) { 38if (!isDevMode || (isDevMode && useLiveAPI)) {
35 api = LIVE_API; 39 api = LIVE_API;
36 wsApi = LIVE_WS_API; 40 wsApi = LIVE_WS_API;
37 web = LIVE_API_WEBSITE; 41 web = LIVE_API_WEBSITE;
42 todos = PRODUCTION_TODOS_FRONTEND_URL;
38} else if (isDevMode && useLocalAPI) { 43} else if (isDevMode && useLocalAPI) {
39 api = LOCAL_API; 44 api = LOCAL_API;
40 wsApi = LOCAL_WS_API; 45 wsApi = LOCAL_WS_API;
41 web = LOCAL_API_WEBSITE; 46 web = LOCAL_API_WEBSITE;
47 todos = LOCAL_TODOS_FRONTEND_URL;
42} else { 48} else {
43 api = DEV_API; 49 api = DEV_API;
44 wsApi = DEV_WS_API; 50 wsApi = DEV_WS_API;
45 web = DEV_API_WEBSITE; 51 web = DEV_API_WEBSITE;
52 todos = DEVELOPMENT_TODOS_FRONTEND_URL;
46} 53}
47 54
48export const API = api; 55export const API = api;
49export const API_VERSION = 'v1'; 56export const API_VERSION = 'v1';
50export const WS_API = wsApi; 57export const WS_API = wsApi;
51export const WEBSITE = web; 58export const WEBSITE = web;
59export const TODOS_FRONTEND = todos;
diff --git a/src/features/announcements/components/AnnouncementScreen.js b/src/features/announcements/components/AnnouncementScreen.js
index e7c5fe395..03bd5ba41 100644
--- a/src/features/announcements/components/AnnouncementScreen.js
+++ b/src/features/announcements/components/AnnouncementScreen.js
@@ -28,7 +28,7 @@ const smallScreen = '1000px';
28const styles = theme => ({ 28const styles = theme => ({
29 container: { 29 container: {
30 background: theme.colorBackground, 30 background: theme.colorBackground,
31 position: 'absolute', 31 position: 'relative',
32 top: 0, 32 top: 0,
33 zIndex: 140, 33 zIndex: 140,
34 width: '100%', 34 width: '100%',
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js
index a8252acb7..ba9ae2273 100644
--- a/src/features/basicAuth/Component.js
+++ b/src/features/basicAuth/Component.js
@@ -27,7 +27,6 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
27 e.preventDefault(); 27 e.preventDefault();
28 28
29 const values = Form.values(); 29 const values = Form.values();
30 console.log('form submit', values);
31 30
32 sendCredentials(values.user, values.password); 31 sendCredentials(values.user, values.password);
33 resetState(); 32 resetState();
diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.js
new file mode 100644
index 000000000..4d050f90e
--- /dev/null
+++ b/src/features/communityRecipes/index.js
@@ -0,0 +1,28 @@
1import { reaction } from 'mobx';
2import { CommunityRecipesStore } from './store';
3
4const debug = require('debug')('Franz:feature:communityRecipes');
5
6export const DEFAULT_SERVICE_LIMIT = 3;
7
8export const communityRecipesStore = new CommunityRecipesStore();
9
10export default function initCommunityRecipes(stores, actions) {
11 const { features } = stores;
12
13 communityRecipesStore.start(stores, actions);
14
15 // Toggle communityRecipe premium status
16 reaction(
17 () => (
18 features.features.isCommunityRecipesIncludedInCurrentPlan
19 ),
20 (isPremiumFeature) => {
21 debug('Community recipes is premium feature: ', isPremiumFeature);
22 communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan = isPremiumFeature;
23 },
24 {
25 fireImmediately: true,
26 },
27 );
28}
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js
new file mode 100644
index 000000000..4d45c3b33
--- /dev/null
+++ b/src/features/communityRecipes/store.js
@@ -0,0 +1,31 @@
1import { computed, observable } from 'mobx';
2import { FeatureStore } from '../utils/FeatureStore';
3
4const debug = require('debug')('Franz:feature:communityRecipes:store');
5
6export class CommunityRecipesStore extends FeatureStore {
7 @observable isCommunityRecipesIncludedInCurrentPlan = false;
8
9 start(stores, actions) {
10 debug('start');
11 this.stores = stores;
12 this.actions = actions;
13 }
14
15 stop() {
16 debug('stop');
17 super.stop();
18 }
19
20 @computed get communityRecipes() {
21 if (!this.stores) return [];
22
23 return this.stores.recipePreviews.dev.map((r) => {
24 r.isDevRecipe = !!r.author.find(a => a.email === this.stores.user.data.email);
25
26 return r;
27 });
28 }
29}
30
31export default CommunityRecipesStore;
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js
index ff0f1f2f8..de5653f04 100644
--- a/src/features/delayApp/Component.js
+++ b/src/features/delayApp/Component.js
@@ -4,29 +4,39 @@ import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6 6
7import { Button } from '@meetfranz/forms';
7import { gaEvent } from '../../lib/analytics'; 8import { gaEvent } from '../../lib/analytics';
8 9
9import Button from '../../components/ui/Button'; 10// import Button from '../../components/ui/Button';
10 11
11import { config } from '.'; 12import { config } from '.';
12import styles from './styles'; 13import styles from './styles';
14import UserStore from '../../stores/UserStore';
13 15
14const messages = defineMessages({ 16const messages = defineMessages({
15 headline: { 17 headline: {
16 id: 'feature.delayApp.headline', 18 id: 'feature.delayApp.headline',
17 defaultMessage: '!!!Please purchase license to skip waiting', 19 defaultMessage: '!!!Please purchase license to skip waiting',
18 }, 20 },
21 headlineTrial: {
22 id: 'feature.delayApp.trial.headline',
23 defaultMessage: '!!!Get the free Franz Professional 14 day trial and skip the line',
24 },
19 action: { 25 action: {
20 id: 'feature.delayApp.action', 26 id: 'feature.delayApp.upgrade.action',
21 defaultMessage: '!!!Get a Franz Supporter License', 27 defaultMessage: '!!!Get a Franz Supporter License',
22 }, 28 },
29 actionTrial: {
30 id: 'feature.delayApp.trial.action',
31 defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional',
32 },
23 text: { 33 text: {
24 id: 'feature.delayApp.text', 34 id: 'feature.delayApp.text',
25 defaultMessage: '!!!Franz will continue in {seconds} seconds.', 35 defaultMessage: '!!!Franz will continue in {seconds} seconds.',
26 }, 36 },
27}); 37});
28 38
29export default @inject('actions') @injectSheet(styles) @observer class DelayApp extends Component { 39export default @inject('stores', 'actions') @injectSheet(styles) @observer class DelayApp extends Component {
30 static propTypes = { 40 static propTypes = {
31 // eslint-disable-next-line 41 // eslint-disable-next-line
32 classes: PropTypes.object.isRequired, 42 classes: PropTypes.object.isRequired,
@@ -62,25 +72,37 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
62 } 72 }
63 73
64 handleCTAClick() { 74 handleCTAClick() {
65 const { actions } = this.props; 75 const { actions, stores } = this.props;
76 const { hadSubscription } = stores.user.data;
77 const { defaultTrialPlan } = stores.features.features;
78
79 if (!hadSubscription) {
80 console.log('directly activate trial');
81 actions.user.activateTrial({ planId: defaultTrialPlan });
66 82
67 actions.ui.openSettings({ path: 'user' }); 83 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature');
84 } else {
85 actions.ui.openSettings({ path: 'user' });
68 86
69 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); 87 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature');
88 }
70 } 89 }
71 90
72 render() { 91 render() {
73 const { classes } = this.props; 92 const { classes, stores } = this.props;
74 const { intl } = this.context; 93 const { intl } = this.context;
75 94
95 const { hadSubscription } = stores.user.data;
96
76 return ( 97 return (
77 <div className={`${classes.container}`}> 98 <div className={`${classes.container}`}>
78 <h1 className={classes.headline}>{intl.formatMessage(messages.headline)}</h1> 99 <h1 className={classes.headline}>{intl.formatMessage(hadSubscription ? messages.headline : messages.headlineTrial)}</h1>
79 <Button 100 <Button
80 label={intl.formatMessage(messages.action)} 101 label={intl.formatMessage(hadSubscription ? messages.action : messages.actionTrial)}
81 className={classes.button} 102 className={classes.button}
82 buttonType="inverted" 103 buttonType="inverted"
83 onClick={this.handleCTAClick.bind(this)} 104 onClick={this.handleCTAClick.bind(this)}
105 busy={stores.user.activateTrialRequest.isExecuting}
84 /> 106 />
85 <p className="footnote"> 107 <p className="footnote">
86 {intl.formatMessage(messages.text, { 108 {intl.formatMessage(messages.text, {
@@ -93,6 +115,9 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
93} 115}
94 116
95DelayApp.wrappedComponent.propTypes = { 117DelayApp.wrappedComponent.propTypes = {
118 stores: PropTypes.shape({
119 user: PropTypes.instanceOf(UserStore).isRequired,
120 }).isRequired,
96 actions: PropTypes.shape({ 121 actions: PropTypes.shape({
97 ui: PropTypes.shape({ 122 ui: PropTypes.shape({
98 openSettings: PropTypes.func.isRequired, 123 openSettings: PropTypes.func.isRequired,
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 67f0fc5e6..627537de7 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -33,7 +33,7 @@ export default function init(stores) {
33 }; 33 };
34 34
35 reaction( 35 reaction(
36 () => stores.user.isLoggedIn && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, 36 () => stores.user.isLoggedIn && stores.services.allServicesRequest.wasExecuted && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium,
37 (isEnabled) => { 37 (isEnabled) => {
38 if (isEnabled) { 38 if (isEnabled) {
39 debug('Enabling `delayApp` feature'); 39 debug('Enabling `delayApp` feature');
@@ -44,7 +44,8 @@ export default function init(stores) {
44 config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; 44 config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait;
45 45
46 autorun(() => { 46 autorun(() => {
47 if (stores.services.all.length === 0) { 47 if (stores.services.allDisplayed.length === 0) {
48 debug('seas', stores.services.all.length);
48 shownAfterLaunch = true; 49 shownAfterLaunch = true;
49 return; 50 return;
50 } 51 }
@@ -64,7 +65,7 @@ export default function init(stores) {
64 debug('Resetting app delay'); 65 debug('Resetting app delay');
65 66
66 setVisibility(false); 67 setVisibility(false);
67 }, DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait + 1000); // timer needs to be able to hit 0 68 }, config.delayDuration + 1000); // timer needs to be able to hit 0
68 } 69 }
69 }); 70 });
70 } else { 71 } else {
diff --git a/src/features/delayApp/styles.js b/src/features/delayApp/styles.js
index 5c214cfdf..69c3c7a27 100644
--- a/src/features/delayApp/styles.js
+++ b/src/features/delayApp/styles.js
@@ -1,7 +1,6 @@
1export default theme => ({ 1export default theme => ({
2 container: { 2 container: {
3 background: theme.colorBackground, 3 background: theme.colorBackground,
4 position: 'absolute',
5 top: 0, 4 top: 0,
6 width: '100%', 5 width: '100%',
7 display: 'flex', 6 display: 'flex',
diff --git a/src/features/serviceLimit/components/LimitReachedInfobox.js b/src/features/serviceLimit/components/LimitReachedInfobox.js
new file mode 100644
index 000000000..fc54dcf85
--- /dev/null
+++ b/src/features/serviceLimit/components/LimitReachedInfobox.js
@@ -0,0 +1,78 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import { Infobox } from '@meetfranz/ui';
7
8import { gaEvent } from '../../../lib/analytics';
9
10const messages = defineMessages({
11 limitReached: {
12 id: 'feature.serviceLimit.limitReached',
13 defaultMessage: '!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.',
14 },
15 action: {
16 id: 'premiumFeature.button.upgradeAccount',
17 defaultMessage: '!!!Upgrade account',
18 },
19});
20
21const styles = theme => ({
22 container: {
23 height: 'auto',
24 background: theme.styleTypes.primary.accent,
25 color: theme.styleTypes.primary.contrast,
26 borderRadius: 0,
27 marginBottom: 0,
28
29 '& > div': {
30 marginBottom: 0,
31 },
32
33 '& button': {
34 color: theme.styleTypes.primary.contrast,
35 },
36 },
37});
38
39
40@inject('stores', 'actions') @injectSheet(styles) @observer
41class LimitReachedInfobox extends Component {
42 static propTypes = {
43 classes: PropTypes.object.isRequired,
44 stores: PropTypes.object.isRequired,
45 actions: PropTypes.object.isRequired,
46 };
47
48 static contextTypes = {
49 intl: intlShape,
50 };
51
52 render() {
53 const { classes, stores, actions } = this.props;
54 const { intl } = this.context;
55
56 const {
57 serviceLimit,
58 } = stores;
59
60 if (!serviceLimit.userHasReachedServiceLimit) return null;
61
62 return (
63 <Infobox
64 icon="mdiInformation"
65 className={classes.container}
66 ctaLabel={intl.formatMessage(messages.action)}
67 ctaOnClick={() => {
68 actions.ui.openSettings({ path: 'user' });
69 gaEvent('Service Limit', 'upgrade', 'Upgrade account');
70 }}
71 >
72 {intl.formatMessage(messages.limitReached, { amount: serviceLimit.serviceCount, limit: serviceLimit.serviceLimit })}
73 </Infobox>
74 );
75 }
76}
77
78export default LimitReachedInfobox;
diff --git a/src/features/serviceLimit/index.js b/src/features/serviceLimit/index.js
new file mode 100644
index 000000000..92ad8bb98
--- /dev/null
+++ b/src/features/serviceLimit/index.js
@@ -0,0 +1,33 @@
1import { reaction } from 'mobx';
2import { ServiceLimitStore } from './store';
3
4const debug = require('debug')('Franz:feature:serviceLimit');
5
6export const DEFAULT_SERVICE_LIMIT = 3;
7
8let store = null;
9
10export const serviceLimitStore = new ServiceLimitStore();
11
12export default function initServiceLimit(stores, actions) {
13 const { features } = stores;
14
15 // Toggle serviceLimit feature
16 reaction(
17 () => (
18 features.features.isServiceLimitEnabled
19 ),
20 (isEnabled) => {
21 if (isEnabled) {
22 debug('Initializing `serviceLimit` feature');
23 store = serviceLimitStore.start(stores, actions);
24 } else if (store) {
25 debug('Disabling `serviceLimit` feature');
26 serviceLimitStore.stop();
27 }
28 },
29 {
30 fireImmediately: true,
31 },
32 );
33}
diff --git a/src/features/serviceLimit/store.js b/src/features/serviceLimit/store.js
new file mode 100644
index 000000000..9836c5f51
--- /dev/null
+++ b/src/features/serviceLimit/store.js
@@ -0,0 +1,41 @@
1import { computed, observable } from 'mobx';
2import { FeatureStore } from '../utils/FeatureStore';
3import { DEFAULT_SERVICE_LIMIT } from '.';
4
5const debug = require('debug')('Franz:feature:serviceLimit:store');
6
7export class ServiceLimitStore extends FeatureStore {
8 @observable isServiceLimitEnabled = false;
9
10 start(stores, actions) {
11 debug('start');
12 this.stores = stores;
13 this.actions = actions;
14
15 this.isServiceLimitEnabled = true;
16 }
17
18 stop() {
19 super.stop();
20
21 this.isServiceLimitEnabled = false;
22 }
23
24 @computed get userHasReachedServiceLimit() {
25 if (!this.isServiceLimitEnabled) return false;
26
27 return this.serviceLimit !== 0 && this.serviceCount >= this.serviceLimit;
28 }
29
30 @computed get serviceLimit() {
31 if (!this.isServiceLimitEnabled || this.stores.features.features.serviceLimitCount === 0) return 0;
32
33 return this.stores.features.features.serviceLimitCount || DEFAULT_SERVICE_LIMIT;
34 }
35
36 @computed get serviceCount() {
37 return this.stores.services.all.length;
38 }
39}
40
41export default ServiceLimitStore;
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js
index 4bea327ad..55c600de4 100644
--- a/src/features/serviceProxy/index.js
+++ b/src/features/serviceProxy/index.js
@@ -9,17 +9,17 @@ const debug = require('debug')('Franz:feature:serviceProxy');
9 9
10export const config = observable({ 10export const config = observable({
11 isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled, 11 isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled,
12 isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature, 12 isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan,
13}); 13});
14 14
15export default function init(stores) { 15export default function init(stores) {
16 debug('Initializing `serviceProxy` feature'); 16 debug('Initializing `serviceProxy` feature');
17 17
18 autorun(() => { 18 autorun(() => {
19 const { isServiceProxyEnabled, isServiceProxyPremiumFeature } = stores.features.features; 19 const { isServiceProxyEnabled, isServiceProxyIncludedInCurrentPlan } = stores.features.features;
20 20
21 config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled; 21 config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled;
22 config.isPremium = isServiceProxyPremiumFeature !== undefined ? isServiceProxyPremiumFeature : DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature; 22 config.isIncludedInCurrentPlan = isServiceProxyIncludedInCurrentPlan !== undefined ? isServiceProxyIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan;
23 23
24 const services = stores.services.enabled; 24 const services = stores.services.enabled;
25 const isPremiumUser = stores.user.data.isPremium; 25 const isPremiumUser = stores.user.data.isPremium;
@@ -30,7 +30,7 @@ export default function init(stores) {
30 services.forEach((service) => { 30 services.forEach((service) => {
31 const s = session.fromPartition(`persist:service-${service.id}`); 31 const s = session.fromPartition(`persist:service-${service.id}`);
32 32
33 if (config.isEnabled && (isPremiumUser || !config.isPremium)) { 33 if (config.isEnabled && (isPremiumUser || !config.isIncludedInCurrentPlan)) {
34 const serviceProxyConfig = proxySettings[service.id]; 34 const serviceProxyConfig = proxySettings[service.id];
35 35
36 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) { 36 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
diff --git a/src/features/shareFranz/Component.js b/src/features/shareFranz/Component.js
index 8d1d595c5..859c0ebe9 100644
--- a/src/features/shareFranz/Component.js
+++ b/src/features/shareFranz/Component.js
@@ -6,6 +6,7 @@ import { defineMessages, intlShape } from 'react-intl';
6import { Button } from '@meetfranz/forms'; 6import { Button } from '@meetfranz/forms';
7import { H1, Icon } from '@meetfranz/ui'; 7import { H1, Icon } from '@meetfranz/ui';
8 8
9import { mdiHeart } from '@mdi/js';
9import Modal from '../../components/ui/Modal'; 10import Modal from '../../components/ui/Modal';
10import { state } from '.'; 11import { state } from '.';
11import { gaEvent } from '../../lib/analytics'; 12import { gaEvent } from '../../lib/analytics';
@@ -116,7 +117,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz
116 close={this.close.bind(this)} 117 close={this.close.bind(this)}
117 > 118 >
118 <div className={classes.heartContainer}> 119 <div className={classes.heartContainer}>
119 <Icon icon="mdiHeart" className={classes.heart} size={4} /> 120 <Icon icon={mdiHeart} className={classes.heart} size={4} />
120 </div> 121 </div>
121 <H1 className={classes.headline}> 122 <H1 className={classes.headline}>
122 {intl.formatMessage(messages.headline)} 123 {intl.formatMessage(messages.headline)}
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js
index 79a2172b4..a07f9f63a 100644
--- a/src/features/spellchecker/index.js
+++ b/src/features/spellchecker/index.js
@@ -5,18 +5,18 @@ import { DEFAULT_FEATURES_CONFIG } from '../../config';
5const debug = require('debug')('Franz:feature:spellchecker'); 5const debug = require('debug')('Franz:feature:spellchecker');
6 6
7export const config = observable({ 7export const config = observable({
8 isPremium: DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature, 8 isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan,
9}); 9});
10 10
11export default function init(stores) { 11export default function init(stores) {
12 debug('Initializing `spellchecker` feature'); 12 debug('Initializing `spellchecker` feature');
13 13
14 autorun(() => { 14 autorun(() => {
15 const { isSpellcheckerPremiumFeature } = stores.features.features; 15 const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features;
16 16
17 config.isPremium = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature; 17 config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan;
18 18
19 if (!stores.user.data.isPremium && config.isPremium && stores.settings.app.enableSpellchecking) { 19 if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) {
20 debug('Override settings.spellcheckerEnabled flag to false'); 20 debug('Override settings.spellcheckerEnabled flag to false');
21 21
22 Object.assign(stores.settings.app, { 22 Object.assign(stores.settings.app, {
diff --git a/src/features/todos/actions.js b/src/features/todos/actions.js
new file mode 100644
index 000000000..dc63d5fcd
--- /dev/null
+++ b/src/features/todos/actions.js
@@ -0,0 +1,22 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const todoActions = createActionsFromDefinitions({
5 resize: {
6 width: PropTypes.number.isRequired,
7 },
8 toggleTodosPanel: {},
9 setTodosWebview: {
10 webview: PropTypes.instanceOf(Element).isRequired,
11 },
12 handleHostMessage: {
13 action: PropTypes.string.isRequired,
14 data: PropTypes.object,
15 },
16 handleClientMessage: {
17 action: PropTypes.string.isRequired,
18 data: PropTypes.object,
19 },
20}, PropTypes.checkPropTypes);
21
22export default todoActions;
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
new file mode 100644
index 000000000..288c1906f
--- /dev/null
+++ b/src/features/todos/components/TodosWebview.js
@@ -0,0 +1,237 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import Webview from 'react-electron-web-view';
6import { Icon } from '@meetfranz/ui';
7
8import * as environment from '../../../environment';
9
10const OPEN_TODOS_BUTTON_SIZE = 45;
11const CLOSE_TODOS_BUTTON_SIZE = 35;
12
13const styles = theme => ({
14 root: {
15 background: theme.colorBackground,
16 position: 'relative',
17 borderLeft: [1, 'solid', theme.todos.todosLayer.borderLeftColor],
18 zIndex: 300,
19
20 transform: ({ isVisible, width }) => `translateX(${isVisible ? 0 : width}px)`,
21
22 '&:hover $closeTodosButton': {
23 opacity: 1,
24 },
25 },
26 webview: {
27 height: '100%',
28
29 '& webview': {
30 height: '100%',
31 },
32 },
33 resizeHandler: {
34 position: 'absolute',
35 left: 0,
36 marginLeft: -5,
37 width: 10,
38 zIndex: 400,
39 cursor: 'col-resize',
40 },
41 dragIndicator: {
42 position: 'absolute',
43 left: 0,
44 width: 5,
45 zIndex: 400,
46 background: theme.todos.dragIndicator.background,
47
48 },
49 openTodosButton: {
50 width: OPEN_TODOS_BUTTON_SIZE,
51 height: OPEN_TODOS_BUTTON_SIZE,
52 background: theme.todos.toggleButton.background,
53 position: 'absolute',
54 bottom: 80,
55 right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)),
56 borderRadius: OPEN_TODOS_BUTTON_SIZE / 2,
57 opacity: props => (props.isVisible ? 0 : 1),
58 transition: 'right 0.5s',
59 zIndex: 600,
60 display: 'flex',
61 alignItems: 'center',
62 justifyContent: 'center',
63 boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor],
64
65 borderTopRightRadius: props => (props.isVisible ? null : 0),
66 borderBottomRightRadius: props => (props.isVisible ? null : 0),
67
68 '& svg': {
69 fill: theme.todos.toggleButton.textColor,
70 transition: 'all 0.5s',
71 },
72 },
73 closeTodosButton: {
74 width: CLOSE_TODOS_BUTTON_SIZE,
75 height: CLOSE_TODOS_BUTTON_SIZE,
76 background: theme.todos.toggleButton.background,
77 position: 'absolute',
78 bottom: 80,
79 right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2),
80 borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2,
81 opacity: 0,
82 transition: 'opacity 0.5s',
83 zIndex: 600,
84 display: 'flex',
85 alignItems: 'center',
86 justifyContent: 'center',
87 boxShadow: [0, 0, 10, theme.todos.toggleButton.shadowColor],
88
89 '& svg': {
90 fill: theme.todos.toggleButton.textColor,
91 },
92 },
93});
94
95@injectSheet(styles) @observer
96class TodosWebview extends Component {
97 static propTypes = {
98 classes: PropTypes.object.isRequired,
99 isVisible: PropTypes.bool.isRequired,
100 togglePanel: PropTypes.func.isRequired,
101 handleClientMessage: PropTypes.func.isRequired,
102 setTodosWebview: PropTypes.func.isRequired,
103 resize: PropTypes.func.isRequired,
104 width: PropTypes.number.isRequired,
105 minWidth: PropTypes.number.isRequired,
106 };
107
108 state = {
109 isDragging: false,
110 width: 300,
111 };
112
113 componentWillMount() {
114 const { width } = this.props;
115
116 this.setState({
117 width,
118 });
119 }
120
121 componentDidMount() {
122 this.node.addEventListener('mousemove', this.resizePanel.bind(this));
123 this.node.addEventListener('mouseup', this.stopResize.bind(this));
124 this.node.addEventListener('mouseleave', this.stopResize.bind(this));
125 }
126
127 startResize = (event) => {
128 this.setState({
129 isDragging: true,
130 initialPos: event.clientX,
131 delta: 0,
132 });
133 };
134
135 resizePanel(e) {
136 const { minWidth } = this.props;
137
138 const {
139 isDragging,
140 initialPos,
141 } = this.state;
142
143 if (isDragging && Math.abs(e.clientX - window.innerWidth) > minWidth) {
144 const delta = e.clientX - initialPos;
145
146 this.setState({
147 delta,
148 });
149 }
150 }
151
152 stopResize() {
153 const {
154 resize,
155 minWidth,
156 } = this.props;
157
158 const {
159 isDragging,
160 delta,
161 width,
162 } = this.state;
163
164 if (isDragging) {
165 let newWidth = width + (delta < 0 ? Math.abs(delta) : -Math.abs(delta));
166
167 if (newWidth < minWidth) {
168 newWidth = minWidth;
169 }
170
171 this.setState({
172 isDragging: false,
173 delta: 0,
174 width: newWidth,
175 });
176
177 resize(newWidth);
178 }
179 }
180
181 startListeningToIpcMessages() {
182 const { handleClientMessage } = this.props;
183 if (!this.webview) return;
184 this.webview.addEventListener('ipc-message', e => handleClientMessage(e.args[0]));
185 }
186
187 render() {
188 const {
189 classes, isVisible, togglePanel,
190 } = this.props;
191 const { width, delta, isDragging } = this.state;
192
193 return (
194 <>
195 <div
196 className={classes.root}
197 style={{ width: isVisible ? width : 0 }}
198 onMouseUp={() => this.stopResize()}
199 ref={(node) => { this.node = node; }}
200 >
201 <button
202 onClick={() => togglePanel()}
203 className={isVisible ? classes.closeTodosButton : classes.openTodosButton}
204 type="button"
205 >
206 <Icon icon={isVisible ? 'mdiChevronRight' : 'mdiCheckAll'} size={2} />
207 </button>
208 <div
209 className={classes.resizeHandler}
210 style={Object.assign({ left: delta }, isDragging ? { width: 600, marginLeft: -200 } : {})} // This hack is required as resizing with webviews beneath behaves quite bad
211 onMouseDown={e => this.startResize(e)}
212 />
213 {isDragging && (
214 <div
215 className={classes.dragIndicator}
216 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad
217 />
218 )}
219 <Webview
220 className={classes.webview}
221 onDidAttach={() => {
222 const { setTodosWebview } = this.props;
223 setTodosWebview(this.webview);
224 this.startListeningToIpcMessages();
225 }}
226 partition="persist:todos"
227 preload="./features/todos/preload.js"
228 ref={(webview) => { this.webview = webview ? webview.view : null; }}
229 src={environment.TODOS_FRONTEND}
230 />
231 </div>
232 </>
233 );
234 }
235}
236
237export default TodosWebview;
diff --git a/src/features/todos/constants.js b/src/features/todos/constants.js
new file mode 100644
index 000000000..2e8a431cc
--- /dev/null
+++ b/src/features/todos/constants.js
@@ -0,0 +1,4 @@
1export const IPC = {
2 TODOS_HOST_CHANNEL: 'TODOS_HOST_CHANNEL',
3 TODOS_CLIENT_CHANNEL: 'TODOS_CLIENT_CHANNEL',
4};
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js
new file mode 100644
index 000000000..d071d0677
--- /dev/null
+++ b/src/features/todos/containers/TodosScreen.js
@@ -0,0 +1,32 @@
1import React, { Component } from 'react';
2import { observer } from 'mobx-react';
3
4import TodosWebview from '../components/TodosWebview';
5import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import { TODOS_MIN_WIDTH, todosStore } from '..';
7import { todoActions } from '../actions';
8
9@observer
10class TodosScreen extends Component {
11 render() {
12 if (!todosStore || !todosStore.isFeatureActive) {
13 return null;
14 }
15
16 return (
17 <ErrorBoundary>
18 <TodosWebview
19 isVisible={todosStore.isTodosPanelVisible}
20 togglePanel={todoActions.toggleTodosPanel}
21 handleClientMessage={todoActions.handleClientMessage}
22 setTodosWebview={webview => todoActions.setTodosWebview({ webview })}
23 width={todosStore.width}
24 minWidth={TODOS_MIN_WIDTH}
25 resize={width => todoActions.resize({ width })}
26 />
27 </ErrorBoundary>
28 );
29 }
30}
31
32export default TodosScreen;
diff --git a/src/features/todos/index.js b/src/features/todos/index.js
new file mode 100644
index 000000000..00b165cc5
--- /dev/null
+++ b/src/features/todos/index.js
@@ -0,0 +1,34 @@
1import { reaction } from 'mobx';
2import TodoStore from './store';
3
4const debug = require('debug')('Franz:feature:todos');
5
6export const GA_CATEGORY_TODOS = 'Todos';
7
8export const DEFAULT_TODOS_WIDTH = 300;
9export const TODOS_MIN_WIDTH = 200;
10export const DEFAULT_TODOS_VISIBLE = true;
11
12export const todosStore = new TodoStore();
13
14export default function initTodos(stores, actions) {
15 stores.todos = todosStore;
16 const { features } = stores;
17
18 // Toggle todos feature
19 reaction(
20 () => features.features.isTodosEnabled,
21 (isEnabled) => {
22 if (isEnabled) {
23 debug('Initializing `todos` feature');
24 todosStore.start(stores, actions);
25 } else if (todosStore.isFeatureActive) {
26 debug('Disabling `todos` feature');
27 todosStore.stop();
28 }
29 },
30 {
31 fireImmediately: true,
32 },
33 );
34}
diff --git a/src/features/todos/preload.js b/src/features/todos/preload.js
new file mode 100644
index 000000000..6e38a2ef3
--- /dev/null
+++ b/src/features/todos/preload.js
@@ -0,0 +1,23 @@
1import { ipcRenderer } from 'electron';
2import { IPC } from './constants';
3
4const debug = require('debug')('Franz:feature:todos:preload');
5
6debug('Preloading Todos Webview');
7
8let hostMessageListener = () => {};
9
10window.franz = {
11 onInitialize(ipcHostMessageListener) {
12 hostMessageListener = ipcHostMessageListener;
13 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, { action: 'todos:initialized' });
14 },
15 sendToHost(message) {
16 ipcRenderer.sendToHost(IPC.TODOS_CLIENT_CHANNEL, message);
17 },
18};
19
20ipcRenderer.on(IPC.TODOS_HOST_CHANNEL, (event, message) => {
21 debug('Received host message', event, message);
22 hostMessageListener(message);
23});
diff --git a/src/features/todos/store.js b/src/features/todos/store.js
new file mode 100644
index 000000000..acf95df0d
--- /dev/null
+++ b/src/features/todos/store.js
@@ -0,0 +1,147 @@
1import { ThemeType } from '@meetfranz/theme';
2import {
3 computed,
4 action,
5 observable,
6} from 'mobx';
7import localStorage from 'mobx-localstorage';
8
9import { todoActions } from './actions';
10import { FeatureStore } from '../utils/FeatureStore';
11import { createReactions } from '../../stores/lib/Reaction';
12import { createActionBindings } from '../utils/ActionBinding';
13import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.';
14import { IPC } from './constants';
15
16const debug = require('debug')('Franz:feature:todos:store');
17
18export default class TodoStore extends FeatureStore {
19 @observable isFeatureEnabled = false;
20
21 @observable isFeatureActive = false;
22
23 webview = null;
24
25 @computed get width() {
26 const width = this.settings.width || DEFAULT_TODOS_WIDTH;
27
28 return width < TODOS_MIN_WIDTH ? TODOS_MIN_WIDTH : width;
29 }
30
31 @computed get isTodosPanelVisible() {
32 if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE;
33
34 return this.settings.isTodosPanelVisible;
35 }
36
37 @computed get settings() {
38 return localStorage.getItem('todos') || {};
39 }
40
41 // ========== PUBLIC API ========= //
42
43 @action start(stores, actions) {
44 debug('TodoStore::start');
45 this.stores = stores;
46 this.actions = actions;
47
48 // ACTIONS
49
50 this._registerActions(createActionBindings([
51 [todoActions.resize, this._resize],
52 [todoActions.toggleTodosPanel, this._toggleTodosPanel],
53 [todoActions.setTodosWebview, this._setTodosWebview],
54 [todoActions.handleHostMessage, this._handleHostMessage],
55 [todoActions.handleClientMessage, this._handleClientMessage],
56 ]));
57
58 // REACTIONS
59
60 this._allReactions = createReactions([
61 this._setFeatureEnabledReaction,
62 ]);
63
64 this._registerReactions(this._allReactions);
65
66 this.isFeatureActive = true;
67 }
68
69 @action stop() {
70 super.stop();
71 debug('TodoStore::stop');
72 this.reset();
73 this.isFeatureActive = false;
74 }
75
76 // ========== PRIVATE METHODS ========= //
77
78 _updateSettings = (changes) => {
79 localStorage.setItem('todos', {
80 ...this.settings,
81 ...changes,
82 });
83 };
84
85 // Actions
86
87 @action _resize = ({ width }) => {
88 this._updateSettings({
89 width,
90 });
91 };
92
93 @action _toggleTodosPanel = () => {
94 this._updateSettings({
95 isTodosPanelVisible: !this.isTodosPanelVisible,
96 });
97 };
98
99 @action _setTodosWebview = ({ webview }) => {
100 debug('_setTodosWebview', webview);
101 this.webview = webview;
102 };
103
104 @action _handleHostMessage = (message) => {
105 debug('_handleHostMessage', message);
106 if (message.action === 'todos:create') {
107 this.webview.send(IPC.TODOS_HOST_CHANNEL, message);
108 }
109 };
110
111 @action _handleClientMessage = (message) => {
112 debug('_handleClientMessage', message);
113 switch (message.action) {
114 case 'todos:initialized': this._onTodosClientInitialized(); break;
115 case 'todos:goToService': this._goToService(message.data); break;
116 default:
117 debug('Unknown client message reiceived', message);
118 }
119 };
120
121 // Todos client message handlers
122
123 _onTodosClientInitialized = () => {
124 this.webview.send(IPC.TODOS_HOST_CHANNEL, {
125 action: 'todos:configure',
126 data: {
127 authToken: this.stores.user.authToken,
128 theme: this.stores.ui.isDarkThemeActive ? ThemeType.dark : ThemeType.default,
129 },
130 });
131 };
132
133 _goToService = ({ url, serviceId }) => {
134 if (url) {
135 this.stores.services.one(serviceId).webview.loadURL(url);
136 }
137 this.actions.service.setActive({ serviceId });
138 };
139
140 // Reactions
141
142 _setFeatureEnabledReaction = () => {
143 const { isTodosEnabled } = this.stores.features.features;
144
145 this.isFeatureEnabled = isTodosEnabled;
146 };
147}
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
index 07b16ff23..e44569be9 100644
--- a/src/features/workspaces/store.js
+++ b/src/features/workspaces/store.js
@@ -79,7 +79,7 @@ export default class WorkspacesStore extends FeatureStore {
79 79
80 // ========== PUBLIC API ========= // 80 // ========== PUBLIC API ========= //
81 81
82 start(stores, actions) { 82 @action start(stores, actions) {
83 debug('WorkspacesStore::start'); 83 debug('WorkspacesStore::start');
84 this.stores = stores; 84 this.stores = stores;
85 this.actions = actions; 85 this.actions = actions;
@@ -104,7 +104,7 @@ export default class WorkspacesStore extends FeatureStore {
104 // REACTIONS 104 // REACTIONS
105 105
106 this._freeUserReactions = createReactions([ 106 this._freeUserReactions = createReactions([
107 this._stopPremiumActionsAndReactions, 107 this._disablePremiumFeatures,
108 this._openDrawerWithSettingsReaction, 108 this._openDrawerWithSettingsReaction,
109 this._setFeatureEnabledReaction, 109 this._setFeatureEnabledReaction,
110 this._setIsPremiumFeatureReaction, 110 this._setIsPremiumFeatureReaction,
@@ -123,10 +123,7 @@ export default class WorkspacesStore extends FeatureStore {
123 this.isFeatureActive = true; 123 this.isFeatureActive = true;
124 } 124 }
125 125
126 stop() { 126 @action reset() {
127 super.stop();
128 debug('WorkspacesStore::stop');
129 this.isFeatureActive = false;
130 this.activeWorkspace = null; 127 this.activeWorkspace = null;
131 this.nextWorkspace = null; 128 this.nextWorkspace = null;
132 this.workspaceBeingEdited = null; 129 this.workspaceBeingEdited = null;
@@ -134,6 +131,13 @@ export default class WorkspacesStore extends FeatureStore {
134 this.isWorkspaceDrawerOpen = false; 131 this.isWorkspaceDrawerOpen = false;
135 } 132 }
136 133
134 @action stop() {
135 super.stop();
136 debug('WorkspacesStore::stop');
137 this.reset();
138 this.isFeatureActive = false;
139 }
140
137 filterServicesByActiveWorkspace = (services) => { 141 filterServicesByActiveWorkspace = (services) => {
138 const { activeWorkspace, isFeatureActive } = this; 142 const { activeWorkspace, isFeatureActive } = this;
139 if (isFeatureActive && activeWorkspace) { 143 if (isFeatureActive && activeWorkspace) {
@@ -251,9 +255,9 @@ export default class WorkspacesStore extends FeatureStore {
251 _setIsPremiumFeatureReaction = () => { 255 _setIsPremiumFeatureReaction = () => {
252 const { features, user } = this.stores; 256 const { features, user } = this.stores;
253 const { isPremium } = user.data; 257 const { isPremium } = user.data;
254 const { isWorkspacePremiumFeature } = features.features; 258 const { isWorkspaceIncludedInCurrentPlan } = features.features;
255 this.isPremiumFeature = isWorkspacePremiumFeature; 259 this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan;
256 this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium; 260 this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan && !isPremium;
257 }; 261 };
258 262
259 _setWorkspaceBeingEditedReaction = () => { 263 _setWorkspaceBeingEditedReaction = () => {
@@ -281,6 +285,7 @@ export default class WorkspacesStore extends FeatureStore {
281 }; 285 };
282 286
283 _activateLastUsedWorkspaceReaction = () => { 287 _activateLastUsedWorkspaceReaction = () => {
288 debug('_activateLastUsedWorkspaceReaction');
284 if (!this.activeWorkspace && this.userHasWorkspaces) { 289 if (!this.activeWorkspace && this.userHasWorkspaces) {
285 const { lastActiveWorkspace } = this.settings; 290 const { lastActiveWorkspace } = this.settings;
286 if (lastActiveWorkspace) { 291 if (lastActiveWorkspace) {
@@ -324,10 +329,12 @@ export default class WorkspacesStore extends FeatureStore {
324 }); 329 });
325 }; 330 };
326 331
327 _stopPremiumActionsAndReactions = () => { 332 _disablePremiumFeatures = () => {
328 if (!this.isUserAllowedToUseFeature) { 333 if (!this.isUserAllowedToUseFeature) {
334 debug('_disablePremiumFeatures');
329 this._stopActions(this._premiumUserActions); 335 this._stopActions(this._premiumUserActions);
330 this._stopReactions(this._premiumUserReactions); 336 this._stopReactions(this._premiumUserReactions);
337 this.reset();
331 } else { 338 } else {
332 this._startActions(this._premiumUserActions); 339 this._startActions(this._premiumUserActions);
333 this._startReactions(this._premiumUserReactions); 340 this._startReactions(this._premiumUserReactions);
diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js
new file mode 100644
index 000000000..19392585e
--- /dev/null
+++ b/src/helpers/plan-helpers.js
@@ -0,0 +1,35 @@
1import { defineMessages } from 'react-intl';
2import { PLANS_MAPPING, PLANS } from '../config';
3
4const messages = defineMessages({
5 [PLANS.PRO]: {
6 id: 'pricing.plan.pro',
7 defaultMessage: '!!!Franz Professional',
8 },
9 [PLANS.PERSONAL]: {
10 id: 'pricing.plan.personal',
11 defaultMessage: '!!!Franz Personal',
12 },
13 [PLANS.FREE]: {
14 id: 'pricing.plan.free',
15 defaultMessage: '!!!Franz Free',
16 },
17 [PLANS.LEGACY]: {
18 id: 'pricing.plan.legacy',
19 defaultMessage: '!!!Franz Premium',
20 },
21});
22
23export function i18nPlanName(planId, intl) {
24 if (!planId) {
25 throw new Error('planId is required');
26 }
27
28 if (!intl) {
29 throw new Error('intl context is required');
30 }
31
32 const plan = PLANS_MAPPING[planId];
33
34 return intl.formatMessage(messages[plan]);
35}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index d517b456b..62d56ad1f 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -417,55 +417,120 @@
417 { 417 {
418 "descriptors": [ 418 "descriptors": [
419 { 419 {
420 "defaultMessage": "!!!Support Franz", 420 "defaultMessage": "!!!Franz Professional",
421 "end": { 421 "end": {
422 "column": 3, 422 "column": 3,
423 "line": 16 423 "line": 18
424 }, 424 },
425 "file": "src/components/auth/Pricing.js", 425 "file": "src/components/auth/Pricing.js",
426 "id": "pricing.headline", 426 "id": "pricing.trial.headline",
427 "start": { 427 "start": {
428 "column": 12, 428 "column": 12,
429 "line": 13 429 "line": 15
430 } 430 }
431 }, 431 },
432 { 432 {
433 "defaultMessage": "!!!Select your support plan", 433 "defaultMessage": "!!!Your personal welcome offer:",
434 "end": { 434 "end": {
435 "column": 3, 435 "column": 3,
436 "line": 20 436 "line": 22
437 }, 437 },
438 "file": "src/components/auth/Pricing.js", 438 "file": "src/components/auth/Pricing.js",
439 "id": "pricing.support.label", 439 "id": "pricing.trial.subheadline",
440 "start": { 440 "start": {
441 "column": 23, 441 "column": 17,
442 "line": 17 442 "line": 19
443 } 443 }
444 }, 444 },
445 { 445 {
446 "defaultMessage": "!!!Support the development of Franz", 446 "defaultMessage": "!!!No strings attached",
447 "end": { 447 "end": {
448 "column": 3, 448 "column": 3,
449 "line": 24 449 "line": 26
450 },
451 "file": "src/components/auth/Pricing.js",
452 "id": "pricing.trial.terms.headline",
453 "start": {
454 "column": 29,
455 "line": 23
456 }
457 },
458 {
459 "defaultMessage": "!!!No credit card required",
460 "end": {
461 "column": 3,
462 "line": 30
450 }, 463 },
451 "file": "src/components/auth/Pricing.js", 464 "file": "src/components/auth/Pricing.js",
452 "id": "pricing.submit.label", 465 "id": "pricing.trial.terms.noCreditCard",
466 "start": {
467 "column": 16,
468 "line": 27
469 }
470 },
471 {
472 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
473 "end": {
474 "column": 3,
475 "line": 34
476 },
477 "file": "src/components/auth/Pricing.js",
478 "id": "pricing.trial.terms.automaticTrialEnd",
453 "start": { 479 "start": {
454 "column": 21, 480 "column": 21,
455 "line": 21 481 "line": 31
456 } 482 }
457 }, 483 },
458 { 484 {
459 "defaultMessage": "!!!I don't want to support the development of Franz.", 485 "defaultMessage": "!!!Sorry, we could not activate your trial!",
460 "end": { 486 "end": {
461 "column": 3, 487 "column": 3,
462 "line": 28 488 "line": 38
463 }, 489 },
464 "file": "src/components/auth/Pricing.js", 490 "file": "src/components/auth/Pricing.js",
465 "id": "pricing.link.skipPayment", 491 "id": "pricing.trial.error",
466 "start": { 492 "start": {
467 "column": 15, 493 "column": 19,
468 "line": 25 494 "line": 35
495 }
496 },
497 {
498 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional",
499 "end": {
500 "column": 3,
501 "line": 42
502 },
503 "file": "src/components/auth/Pricing.js",
504 "id": "pricing.trial.cta.accept",
505 "start": {
506 "column": 13,
507 "line": 39
508 }
509 },
510 {
511 "defaultMessage": "!!!Continue to Franz",
512 "end": {
513 "column": 3,
514 "line": 46
515 },
516 "file": "src/components/auth/Pricing.js",
517 "id": "pricing.trial.cta.skip",
518 "start": {
519 "column": 11,
520 "line": 43
521 }
522 },
523 {
524 "defaultMessage": "!!!Franz Professional includes:",
525 "end": {
526 "column": 3,
527 "line": 50
528 },
529 "file": "src/components/auth/Pricing.js",
530 "id": "pricing.trial.features.headline",
531 "start": {
532 "column": 20,
533 "line": 47
469 } 534 }
470 } 535 }
471 ], 536 ],
@@ -477,156 +542,143 @@
477 "defaultMessage": "!!!Sign up", 542 "defaultMessage": "!!!Sign up",
478 "end": { 543 "end": {
479 "column": 3, 544 "column": 3,
480 "line": 21 545 "line": 20
481 }, 546 },
482 "file": "src/components/auth/Signup.js", 547 "file": "src/components/auth/Signup.js",
483 "id": "signup.headline", 548 "id": "signup.headline",
484 "start": { 549 "start": {
485 "column": 12, 550 "column": 12,
486 "line": 18 551 "line": 17
487 } 552 }
488 }, 553 },
489 { 554 {
490 "defaultMessage": "!!!Firstname", 555 "defaultMessage": "!!!Firstname",
491 "end": { 556 "end": {
492 "column": 3, 557 "column": 3,
493 "line": 25 558 "line": 24
494 }, 559 },
495 "file": "src/components/auth/Signup.js", 560 "file": "src/components/auth/Signup.js",
496 "id": "signup.firstname.label", 561 "id": "signup.firstname.label",
497 "start": { 562 "start": {
498 "column": 18, 563 "column": 18,
499 "line": 22 564 "line": 21
500 } 565 }
501 }, 566 },
502 { 567 {
503 "defaultMessage": "!!!Lastname", 568 "defaultMessage": "!!!Lastname",
504 "end": { 569 "end": {
505 "column": 3, 570 "column": 3,
506 "line": 29 571 "line": 28
507 }, 572 },
508 "file": "src/components/auth/Signup.js", 573 "file": "src/components/auth/Signup.js",
509 "id": "signup.lastname.label", 574 "id": "signup.lastname.label",
510 "start": { 575 "start": {
511 "column": 17, 576 "column": 17,
512 "line": 26 577 "line": 25
513 } 578 }
514 }, 579 },
515 { 580 {
516 "defaultMessage": "!!!Email address", 581 "defaultMessage": "!!!Email address",
517 "end": { 582 "end": {
518 "column": 3, 583 "column": 3,
519 "line": 33 584 "line": 32
520 }, 585 },
521 "file": "src/components/auth/Signup.js", 586 "file": "src/components/auth/Signup.js",
522 "id": "signup.email.label", 587 "id": "signup.email.label",
523 "start": { 588 "start": {
524 "column": 14, 589 "column": 14,
525 "line": 30 590 "line": 29
526 }
527 },
528 {
529 "defaultMessage": "!!!Company",
530 "end": {
531 "column": 3,
532 "line": 37
533 },
534 "file": "src/components/auth/Signup.js",
535 "id": "signup.company.label",
536 "start": {
537 "column": 16,
538 "line": 34
539 } 591 }
540 }, 592 },
541 { 593 {
542 "defaultMessage": "!!!Password", 594 "defaultMessage": "!!!Password",
543 "end": { 595 "end": {
544 "column": 3, 596 "column": 3,
545 "line": 41 597 "line": 40
546 }, 598 },
547 "file": "src/components/auth/Signup.js", 599 "file": "src/components/auth/Signup.js",
548 "id": "signup.password.label", 600 "id": "signup.password.label",
549 "start": { 601 "start": {
550 "column": 17, 602 "column": 17,
551 "line": 38 603 "line": 37
552 } 604 }
553 }, 605 },
554 { 606 {
555 "defaultMessage": "!!!By creating a Franz account you accept the", 607 "defaultMessage": "!!!By creating a Franz account you accept the",
556 "end": { 608 "end": {
557 "column": 3, 609 "column": 3,
558 "line": 45 610 "line": 44
559 }, 611 },
560 "file": "src/components/auth/Signup.js", 612 "file": "src/components/auth/Signup.js",
561 "id": "signup.legal.info", 613 "id": "signup.legal.info",
562 "start": { 614 "start": {
563 "column": 13, 615 "column": 13,
564 "line": 42 616 "line": 41
565 } 617 }
566 }, 618 },
567 { 619 {
568 "defaultMessage": "!!!Terms of service", 620 "defaultMessage": "!!!Terms of service",
569 "end": { 621 "end": {
570 "column": 3, 622 "column": 3,
571 "line": 49 623 "line": 48
572 }, 624 },
573 "file": "src/components/auth/Signup.js", 625 "file": "src/components/auth/Signup.js",
574 "id": "signup.legal.terms", 626 "id": "signup.legal.terms",
575 "start": { 627 "start": {
576 "column": 9, 628 "column": 9,
577 "line": 46 629 "line": 45
578 } 630 }
579 }, 631 },
580 { 632 {
581 "defaultMessage": "!!!Privacy Statement", 633 "defaultMessage": "!!!Privacy Statement",
582 "end": { 634 "end": {
583 "column": 3, 635 "column": 3,
584 "line": 53 636 "line": 52
585 }, 637 },
586 "file": "src/components/auth/Signup.js", 638 "file": "src/components/auth/Signup.js",
587 "id": "signup.legal.privacy", 639 "id": "signup.legal.privacy",
588 "start": { 640 "start": {
589 "column": 11, 641 "column": 11,
590 "line": 50 642 "line": 49
591 } 643 }
592 }, 644 },
593 { 645 {
594 "defaultMessage": "!!!Create account", 646 "defaultMessage": "!!!Create account",
595 "end": { 647 "end": {
596 "column": 3, 648 "column": 3,
597 "line": 57 649 "line": 56
598 }, 650 },
599 "file": "src/components/auth/Signup.js", 651 "file": "src/components/auth/Signup.js",
600 "id": "signup.submit.label", 652 "id": "signup.submit.label",
601 "start": { 653 "start": {
602 "column": 21, 654 "column": 21,
603 "line": 54 655 "line": 53
604 } 656 }
605 }, 657 },
606 { 658 {
607 "defaultMessage": "!!!Already have an account, sign in?", 659 "defaultMessage": "!!!Already have an account, sign in?",
608 "end": { 660 "end": {
609 "column": 3, 661 "column": 3,
610 "line": 61 662 "line": 60
611 }, 663 },
612 "file": "src/components/auth/Signup.js", 664 "file": "src/components/auth/Signup.js",
613 "id": "signup.link.login", 665 "id": "signup.link.login",
614 "start": { 666 "start": {
615 "column": 13, 667 "column": 13,
616 "line": 58 668 "line": 57
617 } 669 }
618 }, 670 },
619 { 671 {
620 "defaultMessage": "!!!A user with that email address already exists", 672 "defaultMessage": "!!!A user with that email address already exists",
621 "end": { 673 "end": {
622 "column": 3, 674 "column": 3,
623 "line": 65 675 "line": 64
624 }, 676 },
625 "file": "src/components/auth/Signup.js", 677 "file": "src/components/auth/Signup.js",
626 "id": "signup.emailDuplicate", 678 "id": "signup.emailDuplicate",
627 "start": { 679 "start": {
628 "column": 18, 680 "column": 18,
629 "line": 62 681 "line": 61
630 } 682 }
631 } 683 }
632 ], 684 ],
@@ -669,39 +721,39 @@
669 "defaultMessage": "!!!Your services have been updated.", 721 "defaultMessage": "!!!Your services have been updated.",
670 "end": { 722 "end": {
671 "column": 3, 723 "column": 3,
672 "line": 29 724 "line": 31
673 }, 725 },
674 "file": "src/components/layout/AppLayout.js", 726 "file": "src/components/layout/AppLayout.js",
675 "id": "infobar.servicesUpdated", 727 "id": "infobar.servicesUpdated",
676 "start": { 728 "start": {
677 "column": 19, 729 "column": 19,
678 "line": 26 730 "line": 28
679 } 731 }
680 }, 732 },
681 { 733 {
682 "defaultMessage": "!!!Reload services", 734 "defaultMessage": "!!!Reload services",
683 "end": { 735 "end": {
684 "column": 3, 736 "column": 3,
685 "line": 33 737 "line": 35
686 }, 738 },
687 "file": "src/components/layout/AppLayout.js", 739 "file": "src/components/layout/AppLayout.js",
688 "id": "infobar.buttonReloadServices", 740 "id": "infobar.buttonReloadServices",
689 "start": { 741 "start": {
690 "column": 24, 742 "column": 24,
691 "line": 30 743 "line": 32
692 } 744 }
693 }, 745 },
694 { 746 {
695 "defaultMessage": "!!!Could not load services and user information", 747 "defaultMessage": "!!!Could not load services and user information",
696 "end": { 748 "end": {
697 "column": 3, 749 "column": 3,
698 "line": 37 750 "line": 39
699 }, 751 },
700 "file": "src/components/layout/AppLayout.js", 752 "file": "src/components/layout/AppLayout.js",
701 "id": "infobar.requiredRequestsFailed", 753 "id": "infobar.requiredRequestsFailed",
702 "start": { 754 "start": {
703 "column": 26, 755 "column": 26,
704 "line": 34 756 "line": 36
705 } 757 }
706 } 758 }
707 ], 759 ],
@@ -894,29 +946,99 @@
894 { 946 {
895 "descriptors": [ 947 "descriptors": [
896 { 948 {
897 "defaultMessage": "!!!Welcome to Franz", 949 "defaultMessage": "!!!You have reached your service limit.",
898 "end": { 950 "end": {
899 "column": 3, 951 "column": 3,
900 "line": 14 952 "line": 14
901 }, 953 },
954 "file": "src/components/services/content/ServiceRestricted.js",
955 "id": "service.restrictedHandler.serviceLimit.headline",
956 "start": {
957 "column": 24,
958 "line": 11
959 }
960 },
961 {
962 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
963 "end": {
964 "column": 3,
965 "line": 18
966 },
967 "file": "src/components/services/content/ServiceRestricted.js",
968 "id": "service.restrictedHandler.serviceLimit.text",
969 "start": {
970 "column": 20,
971 "line": 15
972 }
973 },
974 {
975 "defaultMessage": "!!!Franz Professional Plan required",
976 "end": {
977 "column": 3,
978 "line": 22
979 },
980 "file": "src/components/services/content/ServiceRestricted.js",
981 "id": "service.restrictedHandler.customUrl.headline",
982 "start": {
983 "column": 21,
984 "line": 19
985 }
986 },
987 {
988 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
989 "end": {
990 "column": 3,
991 "line": 26
992 },
993 "file": "src/components/services/content/ServiceRestricted.js",
994 "id": "service.restrictedHandler.customUrl.text",
995 "start": {
996 "column": 17,
997 "line": 23
998 }
999 },
1000 {
1001 "defaultMessage": "!!!Upgrade Account",
1002 "end": {
1003 "column": 3,
1004 "line": 30
1005 },
1006 "file": "src/components/services/content/ServiceRestricted.js",
1007 "id": "service.restrictedHandler.action",
1008 "start": {
1009 "column": 10,
1010 "line": 27
1011 }
1012 }
1013 ],
1014 "path": "src/components/services/content/ServiceRestricted.json"
1015 },
1016 {
1017 "descriptors": [
1018 {
1019 "defaultMessage": "!!!Welcome to Franz",
1020 "end": {
1021 "column": 3,
1022 "line": 17
1023 },
902 "file": "src/components/services/content/Services.js", 1024 "file": "src/components/services/content/Services.js",
903 "id": "services.welcome", 1025 "id": "services.welcome",
904 "start": { 1026 "start": {
905 "column": 11, 1027 "column": 11,
906 "line": 11 1028 "line": 14
907 } 1029 }
908 }, 1030 },
909 { 1031 {
910 "defaultMessage": "!!!Get started", 1032 "defaultMessage": "!!!Get started",
911 "end": { 1033 "end": {
912 "column": 3, 1034 "column": 3,
913 "line": 18 1035 "line": 21
914 }, 1036 },
915 "file": "src/components/services/content/Services.js", 1037 "file": "src/components/services/content/Services.js",
916 "id": "services.getStarted", 1038 "id": "services.getStarted",
917 "start": { 1039 "start": {
918 "column": 14, 1040 "column": 14,
919 "line": 15 1041 "line": 18
920 } 1042 }
921 } 1043 }
922 ], 1044 ],
@@ -1107,38 +1229,25 @@
1107 "defaultMessage": "!!!Account", 1229 "defaultMessage": "!!!Account",
1108 "end": { 1230 "end": {
1109 "column": 3, 1231 "column": 3,
1110 "line": 17 1232 "line": 21
1111 }, 1233 },
1112 "file": "src/components/settings/account/AccountDashboard.js", 1234 "file": "src/components/settings/account/AccountDashboard.js",
1113 "id": "settings.account.headline", 1235 "id": "settings.account.headline",
1114 "start": { 1236 "start": {
1115 "column": 12, 1237 "column": 12,
1116 "line": 14 1238 "line": 18
1117 } 1239 }
1118 }, 1240 },
1119 { 1241 {
1120 "defaultMessage": "!!!Your Subscription", 1242 "defaultMessage": "!!!Your Subscription",
1121 "end": { 1243 "end": {
1122 "column": 3, 1244 "column": 3,
1123 "line": 21 1245 "line": 25
1124 }, 1246 },
1125 "file": "src/components/settings/account/AccountDashboard.js", 1247 "file": "src/components/settings/account/AccountDashboard.js",
1126 "id": "settings.account.headlineSubscription", 1248 "id": "settings.account.headlineSubscription",
1127 "start": { 1249 "start": {
1128 "column": 24, 1250 "column": 24,
1129 "line": 18
1130 }
1131 },
1132 {
1133 "defaultMessage": "!!!Upgrade your Account",
1134 "end": {
1135 "column": 3,
1136 "line": 25
1137 },
1138 "file": "src/components/settings/account/AccountDashboard.js",
1139 "id": "settings.account.headlineUpgrade",
1140 "start": {
1141 "column": 19,
1142 "line": 22 1251 "line": 22
1143 } 1252 }
1144 }, 1253 },
@@ -1169,133 +1278,198 @@
1169 } 1278 }
1170 }, 1279 },
1171 { 1280 {
1172 "defaultMessage": "!!!Basic Account", 1281 "defaultMessage": "!!!Upgrade to Franz Professional",
1173 "end": { 1282 "end": {
1174 "column": 3, 1283 "column": 3,
1175 "line": 37 1284 "line": 37
1176 }, 1285 },
1177 "file": "src/components/settings/account/AccountDashboard.js", 1286 "file": "src/components/settings/account/AccountDashboard.js",
1287 "id": "settings.account.upgradeToPro.label",
1288 "start": {
1289 "column": 23,
1290 "line": 34
1291 }
1292 },
1293 {
1294 "defaultMessage": "!!!Basic Account",
1295 "end": {
1296 "column": 3,
1297 "line": 41
1298 },
1299 "file": "src/components/settings/account/AccountDashboard.js",
1178 "id": "settings.account.accountType.basic", 1300 "id": "settings.account.accountType.basic",
1179 "start": { 1301 "start": {
1180 "column": 20, 1302 "column": 20,
1181 "line": 34 1303 "line": 38
1182 } 1304 }
1183 }, 1305 },
1184 { 1306 {
1185 "defaultMessage": "!!!Premium Supporter Account", 1307 "defaultMessage": "!!!Premium Supporter Account",
1186 "end": { 1308 "end": {
1187 "column": 3, 1309 "column": 3,
1188 "line": 41 1310 "line": 45
1189 }, 1311 },
1190 "file": "src/components/settings/account/AccountDashboard.js", 1312 "file": "src/components/settings/account/AccountDashboard.js",
1191 "id": "settings.account.accountType.premium", 1313 "id": "settings.account.accountType.premium",
1192 "start": { 1314 "start": {
1193 "column": 22, 1315 "column": 22,
1194 "line": 38 1316 "line": 42
1195 } 1317 }
1196 }, 1318 },
1197 { 1319 {
1198 "defaultMessage": "!!!Edit Account", 1320 "defaultMessage": "!!!Edit Account",
1199 "end": { 1321 "end": {
1200 "column": 3, 1322 "column": 3,
1201 "line": 45 1323 "line": 49
1202 }, 1324 },
1203 "file": "src/components/settings/account/AccountDashboard.js", 1325 "file": "src/components/settings/account/AccountDashboard.js",
1204 "id": "settings.account.account.editButton", 1326 "id": "settings.account.account.editButton",
1205 "start": { 1327 "start": {
1206 "column": 21, 1328 "column": 21,
1207 "line": 42 1329 "line": 46
1208 } 1330 }
1209 }, 1331 },
1210 { 1332 {
1211 "defaultMessage": "!!Invoices", 1333 "defaultMessage": "!!Invoices",
1212 "end": { 1334 "end": {
1213 "column": 3, 1335 "column": 3,
1214 "line": 49 1336 "line": 53
1215 }, 1337 },
1216 "file": "src/components/settings/account/AccountDashboard.js", 1338 "file": "src/components/settings/account/AccountDashboard.js",
1217 "id": "settings.account.headlineInvoices", 1339 "id": "settings.account.headlineInvoices",
1218 "start": { 1340 "start": {
1219 "column": 18, 1341 "column": 18,
1220 "line": 46 1342 "line": 50
1221 } 1343 }
1222 }, 1344 },
1223 { 1345 {
1224 "defaultMessage": "!!!Download", 1346 "defaultMessage": "!!!Download",
1225 "end": { 1347 "end": {
1226 "column": 3, 1348 "column": 3,
1227 "line": 53 1349 "line": 57
1228 }, 1350 },
1229 "file": "src/components/settings/account/AccountDashboard.js", 1351 "file": "src/components/settings/account/AccountDashboard.js",
1230 "id": "settings.account.invoiceDownload", 1352 "id": "settings.account.invoiceDownload",
1231 "start": { 1353 "start": {
1232 "column": 19, 1354 "column": 19,
1233 "line": 50 1355 "line": 54
1234 } 1356 }
1235 }, 1357 },
1236 { 1358 {
1237 "defaultMessage": "!!!Could not load user information", 1359 "defaultMessage": "!!!Could not load user information",
1238 "end": { 1360 "end": {
1239 "column": 3, 1361 "column": 3,
1240 "line": 57 1362 "line": 61
1241 }, 1363 },
1242 "file": "src/components/settings/account/AccountDashboard.js", 1364 "file": "src/components/settings/account/AccountDashboard.js",
1243 "id": "settings.account.userInfoRequestFailed", 1365 "id": "settings.account.userInfoRequestFailed",
1244 "start": { 1366 "start": {
1245 "column": 25, 1367 "column": 25,
1246 "line": 54 1368 "line": 58
1247 } 1369 }
1248 }, 1370 },
1249 { 1371 {
1250 "defaultMessage": "!!!Try again", 1372 "defaultMessage": "!!!Try again",
1251 "end": { 1373 "end": {
1252 "column": 3, 1374 "column": 3,
1253 "line": 61 1375 "line": 65
1254 }, 1376 },
1255 "file": "src/components/settings/account/AccountDashboard.js", 1377 "file": "src/components/settings/account/AccountDashboard.js",
1256 "id": "settings.account.tryReloadUserInfoRequest", 1378 "id": "settings.account.tryReloadUserInfoRequest",
1257 "start": { 1379 "start": {
1258 "column": 28, 1380 "column": 28,
1259 "line": 58 1381 "line": 62
1260 } 1382 }
1261 }, 1383 },
1262 { 1384 {
1263 "defaultMessage": "!!!Delete account", 1385 "defaultMessage": "!!!Delete account",
1264 "end": { 1386 "end": {
1265 "column": 3, 1387 "column": 3,
1266 "line": 65 1388 "line": 69
1267 }, 1389 },
1268 "file": "src/components/settings/account/AccountDashboard.js", 1390 "file": "src/components/settings/account/AccountDashboard.js",
1269 "id": "settings.account.deleteAccount", 1391 "id": "settings.account.deleteAccount",
1270 "start": { 1392 "start": {
1271 "column": 17, 1393 "column": 17,
1272 "line": 62 1394 "line": 66
1273 } 1395 }
1274 }, 1396 },
1275 { 1397 {
1276 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.", 1398 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.",
1277 "end": { 1399 "end": {
1278 "column": 3, 1400 "column": 3,
1279 "line": 69 1401 "line": 73
1280 }, 1402 },
1281 "file": "src/components/settings/account/AccountDashboard.js", 1403 "file": "src/components/settings/account/AccountDashboard.js",
1282 "id": "settings.account.deleteInfo", 1404 "id": "settings.account.deleteInfo",
1283 "start": { 1405 "start": {
1284 "column": 14, 1406 "column": 14,
1285 "line": 66 1407 "line": 70
1286 } 1408 }
1287 }, 1409 },
1288 { 1410 {
1289 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", 1411 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!",
1290 "end": { 1412 "end": {
1291 "column": 3, 1413 "column": 3,
1292 "line": 73 1414 "line": 77
1293 }, 1415 },
1294 "file": "src/components/settings/account/AccountDashboard.js", 1416 "file": "src/components/settings/account/AccountDashboard.js",
1295 "id": "settings.account.deleteEmailSent", 1417 "id": "settings.account.deleteEmailSent",
1296 "start": { 1418 "start": {
1297 "column": 19, 1419 "column": 19,
1298 "line": 70 1420 "line": 74
1421 }
1422 },
1423 {
1424 "defaultMessage": "!!!Free Trial",
1425 "end": {
1426 "column": 3,
1427 "line": 81
1428 },
1429 "file": "src/components/settings/account/AccountDashboard.js",
1430 "id": "settings.account.trial",
1431 "start": {
1432 "column": 9,
1433 "line": 78
1434 }
1435 },
1436 {
1437 "defaultMessage": "!!!Your Franz License:",
1438 "end": {
1439 "column": 3,
1440 "line": 85
1441 },
1442 "file": "src/components/settings/account/AccountDashboard.js",
1443 "id": "settings.account.yourLicense",
1444 "start": {
1445 "column": 15,
1446 "line": 82
1447 }
1448 },
1449 {
1450 "defaultMessage": "!!!Your free trial ends in {duration}.",
1451 "end": {
1452 "column": 3,
1453 "line": 89
1454 },
1455 "file": "src/components/settings/account/AccountDashboard.js",
1456 "id": "settings.account.trialEndsIn",
1457 "start": {
1458 "column": 15,
1459 "line": 86
1460 }
1461 },
1462 {
1463 "defaultMessage": "!!!Please update your billing info to continue using {license} after your trial period.",
1464 "end": {
1465 "column": 3,
1466 "line": 93
1467 },
1468 "file": "src/components/settings/account/AccountDashboard.js",
1469 "id": "settings.account.trialUpdateBillingInfo",
1470 "start": {
1471 "column": 33,
1472 "line": 90
1299 } 1473 }
1300 } 1474 }
1301 ], 1475 ],
@@ -1307,104 +1481,104 @@
1307 "defaultMessage": "!!!Available services", 1481 "defaultMessage": "!!!Available services",
1308 "end": { 1482 "end": {
1309 "column": 3, 1483 "column": 3,
1310 "line": 16 1484 "line": 17
1311 }, 1485 },
1312 "file": "src/components/settings/navigation/SettingsNavigation.js", 1486 "file": "src/components/settings/navigation/SettingsNavigation.js",
1313 "id": "settings.navigation.availableServices", 1487 "id": "settings.navigation.availableServices",
1314 "start": { 1488 "start": {
1315 "column": 21, 1489 "column": 21,
1316 "line": 13 1490 "line": 14
1317 } 1491 }
1318 }, 1492 },
1319 { 1493 {
1320 "defaultMessage": "!!!Your services", 1494 "defaultMessage": "!!!Your services",
1321 "end": { 1495 "end": {
1322 "column": 3, 1496 "column": 3,
1323 "line": 20 1497 "line": 21
1324 }, 1498 },
1325 "file": "src/components/settings/navigation/SettingsNavigation.js", 1499 "file": "src/components/settings/navigation/SettingsNavigation.js",
1326 "id": "settings.navigation.yourServices", 1500 "id": "settings.navigation.yourServices",
1327 "start": { 1501 "start": {
1328 "column": 16, 1502 "column": 16,
1329 "line": 17 1503 "line": 18
1330 } 1504 }
1331 }, 1505 },
1332 { 1506 {
1333 "defaultMessage": "!!!Your workspaces", 1507 "defaultMessage": "!!!Your workspaces",
1334 "end": { 1508 "end": {
1335 "column": 3, 1509 "column": 3,
1336 "line": 24 1510 "line": 25
1337 }, 1511 },
1338 "file": "src/components/settings/navigation/SettingsNavigation.js", 1512 "file": "src/components/settings/navigation/SettingsNavigation.js",
1339 "id": "settings.navigation.yourWorkspaces", 1513 "id": "settings.navigation.yourWorkspaces",
1340 "start": { 1514 "start": {
1341 "column": 18, 1515 "column": 18,
1342 "line": 21 1516 "line": 22
1343 } 1517 }
1344 }, 1518 },
1345 { 1519 {
1346 "defaultMessage": "!!!Account", 1520 "defaultMessage": "!!!Account",
1347 "end": { 1521 "end": {
1348 "column": 3, 1522 "column": 3,
1349 "line": 28 1523 "line": 29
1350 }, 1524 },
1351 "file": "src/components/settings/navigation/SettingsNavigation.js", 1525 "file": "src/components/settings/navigation/SettingsNavigation.js",
1352 "id": "settings.navigation.account", 1526 "id": "settings.navigation.account",
1353 "start": { 1527 "start": {
1354 "column": 11, 1528 "column": 11,
1355 "line": 25 1529 "line": 26
1356 } 1530 }
1357 }, 1531 },
1358 { 1532 {
1359 "defaultMessage": "!!!Manage Team", 1533 "defaultMessage": "!!!Manage Team",
1360 "end": { 1534 "end": {
1361 "column": 3, 1535 "column": 3,
1362 "line": 32 1536 "line": 33
1363 }, 1537 },
1364 "file": "src/components/settings/navigation/SettingsNavigation.js", 1538 "file": "src/components/settings/navigation/SettingsNavigation.js",
1365 "id": "settings.navigation.team", 1539 "id": "settings.navigation.team",
1366 "start": { 1540 "start": {
1367 "column": 8, 1541 "column": 8,
1368 "line": 29 1542 "line": 30
1369 } 1543 }
1370 }, 1544 },
1371 { 1545 {
1372 "defaultMessage": "!!!Settings", 1546 "defaultMessage": "!!!Settings",
1373 "end": { 1547 "end": {
1374 "column": 3, 1548 "column": 3,
1375 "line": 36 1549 "line": 37
1376 }, 1550 },
1377 "file": "src/components/settings/navigation/SettingsNavigation.js", 1551 "file": "src/components/settings/navigation/SettingsNavigation.js",
1378 "id": "settings.navigation.settings", 1552 "id": "settings.navigation.settings",
1379 "start": { 1553 "start": {
1380 "column": 12, 1554 "column": 12,
1381 "line": 33 1555 "line": 34
1382 } 1556 }
1383 }, 1557 },
1384 { 1558 {
1385 "defaultMessage": "!!!Invite Friends", 1559 "defaultMessage": "!!!Invite Friends",
1386 "end": { 1560 "end": {
1387 "column": 3, 1561 "column": 3,
1388 "line": 40 1562 "line": 41
1389 }, 1563 },
1390 "file": "src/components/settings/navigation/SettingsNavigation.js", 1564 "file": "src/components/settings/navigation/SettingsNavigation.js",
1391 "id": "settings.navigation.inviteFriends", 1565 "id": "settings.navigation.inviteFriends",
1392 "start": { 1566 "start": {
1393 "column": 17, 1567 "column": 17,
1394 "line": 37 1568 "line": 38
1395 } 1569 }
1396 }, 1570 },
1397 { 1571 {
1398 "defaultMessage": "!!!Logout", 1572 "defaultMessage": "!!!Logout",
1399 "end": { 1573 "end": {
1400 "column": 3, 1574 "column": 3,
1401 "line": 44 1575 "line": 45
1402 }, 1576 },
1403 "file": "src/components/settings/navigation/SettingsNavigation.js", 1577 "file": "src/components/settings/navigation/SettingsNavigation.js",
1404 "id": "settings.navigation.logout", 1578 "id": "settings.navigation.logout",
1405 "start": { 1579 "start": {
1406 "column": 10, 1580 "column": 10,
1407 "line": 41 1581 "line": 42
1408 } 1582 }
1409 } 1583 }
1410 ], 1584 ],
@@ -1416,104 +1590,182 @@
1416 "defaultMessage": "!!!Available Services", 1590 "defaultMessage": "!!!Available Services",
1417 "end": { 1591 "end": {
1418 "column": 3, 1592 "column": 3,
1419 "line": 18 1593 "line": 23
1420 }, 1594 },
1421 "file": "src/components/settings/recipes/RecipesDashboard.js", 1595 "file": "src/components/settings/recipes/RecipesDashboard.js",
1422 "id": "settings.recipes.headline", 1596 "id": "settings.recipes.headline",
1423 "start": { 1597 "start": {
1424 "column": 12, 1598 "column": 12,
1425 "line": 15 1599 "line": 20
1426 } 1600 }
1427 }, 1601 },
1428 { 1602 {
1429 "defaultMessage": "!!!Search service", 1603 "defaultMessage": "!!!Search service",
1430 "end": { 1604 "end": {
1431 "column": 3, 1605 "column": 3,
1432 "line": 22 1606 "line": 27
1433 }, 1607 },
1434 "file": "src/components/settings/recipes/RecipesDashboard.js", 1608 "file": "src/components/settings/recipes/RecipesDashboard.js",
1435 "id": "settings.searchService", 1609 "id": "settings.searchService",
1436 "start": { 1610 "start": {
1437 "column": 17, 1611 "column": 17,
1438 "line": 19 1612 "line": 24
1439 } 1613 }
1440 }, 1614 },
1441 { 1615 {
1442 "defaultMessage": "!!!Most popular", 1616 "defaultMessage": "!!!Most popular",
1443 "end": { 1617 "end": {
1444 "column": 3, 1618 "column": 3,
1445 "line": 26 1619 "line": 31
1446 }, 1620 },
1447 "file": "src/components/settings/recipes/RecipesDashboard.js", 1621 "file": "src/components/settings/recipes/RecipesDashboard.js",
1448 "id": "settings.recipes.mostPopular", 1622 "id": "settings.recipes.mostPopular",
1449 "start": { 1623 "start": {
1450 "column": 22, 1624 "column": 22,
1451 "line": 23 1625 "line": 28
1452 } 1626 }
1453 }, 1627 },
1454 { 1628 {
1455 "defaultMessage": "!!!All services", 1629 "defaultMessage": "!!!All services",
1456 "end": { 1630 "end": {
1457 "column": 3, 1631 "column": 3,
1458 "line": 30 1632 "line": 35
1459 }, 1633 },
1460 "file": "src/components/settings/recipes/RecipesDashboard.js", 1634 "file": "src/components/settings/recipes/RecipesDashboard.js",
1461 "id": "settings.recipes.all", 1635 "id": "settings.recipes.all",
1462 "start": { 1636 "start": {
1463 "column": 14, 1637 "column": 14,
1464 "line": 27 1638 "line": 32
1465 } 1639 }
1466 }, 1640 },
1467 { 1641 {
1468 "defaultMessage": "!!!Development", 1642 "defaultMessage": "!!!Custom Services",
1469 "end": { 1643 "end": {
1470 "column": 3, 1644 "column": 3,
1471 "line": 34 1645 "line": 39
1472 }, 1646 },
1473 "file": "src/components/settings/recipes/RecipesDashboard.js", 1647 "file": "src/components/settings/recipes/RecipesDashboard.js",
1474 "id": "settings.recipes.dev", 1648 "id": "settings.recipes.custom",
1475 "start": { 1649 "start": {
1476 "column": 14, 1650 "column": 17,
1477 "line": 31 1651 "line": 36
1478 } 1652 }
1479 }, 1653 },
1480 { 1654 {
1481 "defaultMessage": "!!!Sorry, but no service matched your search term.", 1655 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1482 "end": { 1656 "end": {
1483 "column": 3, 1657 "column": 3,
1484 "line": 38 1658 "line": 43
1485 }, 1659 },
1486 "file": "src/components/settings/recipes/RecipesDashboard.js", 1660 "file": "src/components/settings/recipes/RecipesDashboard.js",
1487 "id": "settings.recipes.nothingFound", 1661 "id": "settings.recipes.nothingFound",
1488 "start": { 1662 "start": {
1489 "column": 16, 1663 "column": 16,
1490 "line": 35 1664 "line": 40
1491 } 1665 }
1492 }, 1666 },
1493 { 1667 {
1494 "defaultMessage": "!!!Service successfully added", 1668 "defaultMessage": "!!!Service successfully added",
1495 "end": { 1669 "end": {
1496 "column": 3, 1670 "column": 3,
1497 "line": 42 1671 "line": 47
1498 }, 1672 },
1499 "file": "src/components/settings/recipes/RecipesDashboard.js", 1673 "file": "src/components/settings/recipes/RecipesDashboard.js",
1500 "id": "settings.recipes.servicesSuccessfulAddedInfo", 1674 "id": "settings.recipes.servicesSuccessfulAddedInfo",
1501 "start": { 1675 "start": {
1502 "column": 31, 1676 "column": 31,
1503 "line": 39 1677 "line": 44
1504 } 1678 }
1505 }, 1679 },
1506 { 1680 {
1507 "defaultMessage": "!!!Missing a service?", 1681 "defaultMessage": "!!!Missing a service?",
1508 "end": { 1682 "end": {
1509 "column": 3, 1683 "column": 3,
1510 "line": 46 1684 "line": 51
1511 }, 1685 },
1512 "file": "src/components/settings/recipes/RecipesDashboard.js", 1686 "file": "src/components/settings/recipes/RecipesDashboard.js",
1513 "id": "settings.recipes.missingService", 1687 "id": "settings.recipes.missingService",
1514 "start": { 1688 "start": {
1515 "column": 18, 1689 "column": 18,
1516 "line": 43 1690 "line": 48
1691 }
1692 },
1693 {
1694 "defaultMessage": "!!!To add a custom service, copy the recipe folder into:",
1695 "end": {
1696 "column": 3,
1697 "line": 55
1698 },
1699 "file": "src/components/settings/recipes/RecipesDashboard.js",
1700 "id": "settings.recipes.customService.intro",
1701 "start": {
1702 "column": 21,
1703 "line": 52
1704 }
1705 },
1706 {
1707 "defaultMessage": "!!!Open directory",
1708 "end": {
1709 "column": 3,
1710 "line": 59
1711 },
1712 "file": "src/components/settings/recipes/RecipesDashboard.js",
1713 "id": "settings.recipes.customService.openFolder",
1714 "start": {
1715 "column": 14,
1716 "line": 56
1717 }
1718 },
1719 {
1720 "defaultMessage": "!!!Developer Documentation",
1721 "end": {
1722 "column": 3,
1723 "line": 63
1724 },
1725 "file": "src/components/settings/recipes/RecipesDashboard.js",
1726 "id": "settings.recipes.customService.openDevDocs",
1727 "start": {
1728 "column": 15,
1729 "line": 60
1730 }
1731 },
1732 {
1733 "defaultMessage": "!!!Custom Service Recipes",
1734 "end": {
1735 "column": 3,
1736 "line": 67
1737 },
1738 "file": "src/components/settings/recipes/RecipesDashboard.js",
1739 "id": "settings.recipes.customService.headline.customRecipes",
1740 "start": {
1741 "column": 25,
1742 "line": 64
1743 }
1744 },
1745 {
1746 "defaultMessage": "!!!Community Services",
1747 "end": {
1748 "column": 3,
1749 "line": 71
1750 },
1751 "file": "src/components/settings/recipes/RecipesDashboard.js",
1752 "id": "settings.recipes.customService.headline.communityRecipes",
1753 "start": {
1754 "column": 28,
1755 "line": 68
1756 }
1757 },
1758 {
1759 "defaultMessage": "!!!Your Development Service Recipes",
1760 "end": {
1761 "column": 3,
1762 "line": 75
1763 },
1764 "file": "src/components/settings/recipes/RecipesDashboard.js",
1765 "id": "settings.recipes.customService.headline.devRecipes",
1766 "start": {
1767 "column": 22,
1768 "line": 72
1517 } 1769 }
1518 } 1770 }
1519 ], 1771 ],
@@ -1525,286 +1777,286 @@
1525 "defaultMessage": "!!!Save service", 1777 "defaultMessage": "!!!Save service",
1526 "end": { 1778 "end": {
1527 "column": 3, 1779 "column": 3,
1528 "line": 25 1780 "line": 27
1529 }, 1781 },
1530 "file": "src/components/settings/services/EditServiceForm.js", 1782 "file": "src/components/settings/services/EditServiceForm.js",
1531 "id": "settings.service.form.saveButton", 1783 "id": "settings.service.form.saveButton",
1532 "start": { 1784 "start": {
1533 "column": 15, 1785 "column": 15,
1534 "line": 22 1786 "line": 24
1535 } 1787 }
1536 }, 1788 },
1537 { 1789 {
1538 "defaultMessage": "!!!Delete Service", 1790 "defaultMessage": "!!!Delete Service",
1539 "end": { 1791 "end": {
1540 "column": 3, 1792 "column": 3,
1541 "line": 29 1793 "line": 31
1542 }, 1794 },
1543 "file": "src/components/settings/services/EditServiceForm.js", 1795 "file": "src/components/settings/services/EditServiceForm.js",
1544 "id": "settings.service.form.deleteButton", 1796 "id": "settings.service.form.deleteButton",
1545 "start": { 1797 "start": {
1546 "column": 17, 1798 "column": 17,
1547 "line": 26 1799 "line": 28
1548 } 1800 }
1549 }, 1801 },
1550 { 1802 {
1551 "defaultMessage": "!!!Available services", 1803 "defaultMessage": "!!!Available services",
1552 "end": { 1804 "end": {
1553 "column": 3, 1805 "column": 3,
1554 "line": 33 1806 "line": 35
1555 }, 1807 },
1556 "file": "src/components/settings/services/EditServiceForm.js", 1808 "file": "src/components/settings/services/EditServiceForm.js",
1557 "id": "settings.service.form.availableServices", 1809 "id": "settings.service.form.availableServices",
1558 "start": { 1810 "start": {
1559 "column": 21, 1811 "column": 21,
1560 "line": 30 1812 "line": 32
1561 } 1813 }
1562 }, 1814 },
1563 { 1815 {
1564 "defaultMessage": "!!!Your services", 1816 "defaultMessage": "!!!Your services",
1565 "end": { 1817 "end": {
1566 "column": 3, 1818 "column": 3,
1567 "line": 37 1819 "line": 39
1568 }, 1820 },
1569 "file": "src/components/settings/services/EditServiceForm.js", 1821 "file": "src/components/settings/services/EditServiceForm.js",
1570 "id": "settings.service.form.yourServices", 1822 "id": "settings.service.form.yourServices",
1571 "start": { 1823 "start": {
1572 "column": 16, 1824 "column": 16,
1573 "line": 34 1825 "line": 36
1574 } 1826 }
1575 }, 1827 },
1576 { 1828 {
1577 "defaultMessage": "!!!Add {name}", 1829 "defaultMessage": "!!!Add {name}",
1578 "end": { 1830 "end": {
1579 "column": 3, 1831 "column": 3,
1580 "line": 41 1832 "line": 43
1581 }, 1833 },
1582 "file": "src/components/settings/services/EditServiceForm.js", 1834 "file": "src/components/settings/services/EditServiceForm.js",
1583 "id": "settings.service.form.addServiceHeadline", 1835 "id": "settings.service.form.addServiceHeadline",
1584 "start": { 1836 "start": {
1585 "column": 22, 1837 "column": 22,
1586 "line": 38 1838 "line": 40
1587 } 1839 }
1588 }, 1840 },
1589 { 1841 {
1590 "defaultMessage": "!!!Edit {name}", 1842 "defaultMessage": "!!!Edit {name}",
1591 "end": { 1843 "end": {
1592 "column": 3, 1844 "column": 3,
1593 "line": 45 1845 "line": 47
1594 }, 1846 },
1595 "file": "src/components/settings/services/EditServiceForm.js", 1847 "file": "src/components/settings/services/EditServiceForm.js",
1596 "id": "settings.service.form.editServiceHeadline", 1848 "id": "settings.service.form.editServiceHeadline",
1597 "start": { 1849 "start": {
1598 "column": 23, 1850 "column": 23,
1599 "line": 42 1851 "line": 44
1600 } 1852 }
1601 }, 1853 },
1602 { 1854 {
1603 "defaultMessage": "!!!Hosted", 1855 "defaultMessage": "!!!Hosted",
1604 "end": { 1856 "end": {
1605 "column": 3, 1857 "column": 3,
1606 "line": 49 1858 "line": 51
1607 }, 1859 },
1608 "file": "src/components/settings/services/EditServiceForm.js", 1860 "file": "src/components/settings/services/EditServiceForm.js",
1609 "id": "settings.service.form.tabHosted", 1861 "id": "settings.service.form.tabHosted",
1610 "start": { 1862 "start": {
1611 "column": 13, 1863 "column": 13,
1612 "line": 46 1864 "line": 48
1613 } 1865 }
1614 }, 1866 },
1615 { 1867 {
1616 "defaultMessage": "!!!Self hosted ⭐️", 1868 "defaultMessage": "!!!Self hosted ⭐️",
1617 "end": { 1869 "end": {
1618 "column": 3, 1870 "column": 3,
1619 "line": 53 1871 "line": 55
1620 }, 1872 },
1621 "file": "src/components/settings/services/EditServiceForm.js", 1873 "file": "src/components/settings/services/EditServiceForm.js",
1622 "id": "settings.service.form.tabOnPremise", 1874 "id": "settings.service.form.tabOnPremise",
1623 "start": { 1875 "start": {
1624 "column": 16, 1876 "column": 16,
1625 "line": 50 1877 "line": 52
1626 } 1878 }
1627 }, 1879 },
1628 { 1880 {
1629 "defaultMessage": "!!!Use the hosted {name} service.", 1881 "defaultMessage": "!!!Use the hosted {name} service.",
1630 "end": { 1882 "end": {
1631 "column": 3, 1883 "column": 3,
1632 "line": 57 1884 "line": 59
1633 }, 1885 },
1634 "file": "src/components/settings/services/EditServiceForm.js", 1886 "file": "src/components/settings/services/EditServiceForm.js",
1635 "id": "settings.service.form.useHostedService", 1887 "id": "settings.service.form.useHostedService",
1636 "start": { 1888 "start": {
1637 "column": 20, 1889 "column": 20,
1638 "line": 54 1890 "line": 56
1639 } 1891 }
1640 }, 1892 },
1641 { 1893 {
1642 "defaultMessage": "!!!Could not validate custom {name} server.", 1894 "defaultMessage": "!!!Could not validate custom {name} server.",
1643 "end": { 1895 "end": {
1644 "column": 3, 1896 "column": 3,
1645 "line": 61 1897 "line": 63
1646 }, 1898 },
1647 "file": "src/components/settings/services/EditServiceForm.js", 1899 "file": "src/components/settings/services/EditServiceForm.js",
1648 "id": "settings.service.form.customUrlValidationError", 1900 "id": "settings.service.form.customUrlValidationError",
1649 "start": { 1901 "start": {
1650 "column": 28, 1902 "column": 28,
1651 "line": 58 1903 "line": 60
1652 } 1904 }
1653 }, 1905 },
1654 { 1906 {
1655 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", 1907 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.",
1656 "end": { 1908 "end": {
1657 "column": 3, 1909 "column": 3,
1658 "line": 65 1910 "line": 67
1659 }, 1911 },
1660 "file": "src/components/settings/services/EditServiceForm.js", 1912 "file": "src/components/settings/services/EditServiceForm.js",
1661 "id": "settings.service.form.customUrlPremiumInfo", 1913 "id": "settings.service.form.customUrlPremiumInfo",
1662 "start": { 1914 "start": {
1663 "column": 24, 1915 "column": 24,
1664 "line": 62 1916 "line": 64
1665 } 1917 }
1666 }, 1918 },
1667 { 1919 {
1668 "defaultMessage": "!!!Upgrade your account", 1920 "defaultMessage": "!!!Upgrade your account",
1669 "end": { 1921 "end": {
1670 "column": 3, 1922 "column": 3,
1671 "line": 69 1923 "line": 71
1672 }, 1924 },
1673 "file": "src/components/settings/services/EditServiceForm.js", 1925 "file": "src/components/settings/services/EditServiceForm.js",
1674 "id": "settings.service.form.customUrlUpgradeAccount", 1926 "id": "settings.service.form.customUrlUpgradeAccount",
1675 "start": { 1927 "start": {
1676 "column": 27, 1928 "column": 27,
1677 "line": 66 1929 "line": 68
1678 } 1930 }
1679 }, 1931 },
1680 { 1932 {
1681 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 1933 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
1682 "end": { 1934 "end": {
1683 "column": 3, 1935 "column": 3,
1684 "line": 73 1936 "line": 75
1685 }, 1937 },
1686 "file": "src/components/settings/services/EditServiceForm.js", 1938 "file": "src/components/settings/services/EditServiceForm.js",
1687 "id": "settings.service.form.indirectMessageInfo", 1939 "id": "settings.service.form.indirectMessageInfo",
1688 "start": { 1940 "start": {
1689 "column": 23, 1941 "column": 23,
1690 "line": 70 1942 "line": 72
1691 } 1943 }
1692 }, 1944 },
1693 { 1945 {
1694 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 1946 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
1695 "end": { 1947 "end": {
1696 "column": 3, 1948 "column": 3,
1697 "line": 77 1949 "line": 79
1698 }, 1950 },
1699 "file": "src/components/settings/services/EditServiceForm.js", 1951 "file": "src/components/settings/services/EditServiceForm.js",
1700 "id": "settings.service.form.isMutedInfo", 1952 "id": "settings.service.form.isMutedInfo",
1701 "start": { 1953 "start": {
1702 "column": 15, 1954 "column": 15,
1703 "line": 74 1955 "line": 76
1704 } 1956 }
1705 }, 1957 },
1706 { 1958 {
1707 "defaultMessage": "!!!Notifications", 1959 "defaultMessage": "!!!Notifications",
1708 "end": { 1960 "end": {
1709 "column": 3, 1961 "column": 3,
1710 "line": 81 1962 "line": 83
1711 }, 1963 },
1712 "file": "src/components/settings/services/EditServiceForm.js", 1964 "file": "src/components/settings/services/EditServiceForm.js",
1713 "id": "settings.service.form.headlineNotifications", 1965 "id": "settings.service.form.headlineNotifications",
1714 "start": { 1966 "start": {
1715 "column": 25, 1967 "column": 25,
1716 "line": 78 1968 "line": 80
1717 } 1969 }
1718 }, 1970 },
1719 { 1971 {
1720 "defaultMessage": "!!!Unread message badges", 1972 "defaultMessage": "!!!Unread message badges",
1721 "end": { 1973 "end": {
1722 "column": 3, 1974 "column": 3,
1723 "line": 85 1975 "line": 87
1724 }, 1976 },
1725 "file": "src/components/settings/services/EditServiceForm.js", 1977 "file": "src/components/settings/services/EditServiceForm.js",
1726 "id": "settings.service.form.headlineBadges", 1978 "id": "settings.service.form.headlineBadges",
1727 "start": { 1979 "start": {
1728 "column": 18, 1980 "column": 18,
1729 "line": 82 1981 "line": 84
1730 } 1982 }
1731 }, 1983 },
1732 { 1984 {
1733 "defaultMessage": "!!!General", 1985 "defaultMessage": "!!!General",
1734 "end": { 1986 "end": {
1735 "column": 3, 1987 "column": 3,
1736 "line": 89 1988 "line": 91
1737 }, 1989 },
1738 "file": "src/components/settings/services/EditServiceForm.js", 1990 "file": "src/components/settings/services/EditServiceForm.js",
1739 "id": "settings.service.form.headlineGeneral", 1991 "id": "settings.service.form.headlineGeneral",
1740 "start": { 1992 "start": {
1741 "column": 19, 1993 "column": 19,
1742 "line": 86 1994 "line": 88
1743 } 1995 }
1744 }, 1996 },
1745 { 1997 {
1746 "defaultMessage": "!!!Delete", 1998 "defaultMessage": "!!!Delete",
1747 "end": { 1999 "end": {
1748 "column": 3, 2000 "column": 3,
1749 "line": 93 2001 "line": 95
1750 }, 2002 },
1751 "file": "src/components/settings/services/EditServiceForm.js", 2003 "file": "src/components/settings/services/EditServiceForm.js",
1752 "id": "settings.service.form.iconDelete", 2004 "id": "settings.service.form.iconDelete",
1753 "start": { 2005 "start": {
1754 "column": 14, 2006 "column": 14,
1755 "line": 90 2007 "line": 92
1756 } 2008 }
1757 }, 2009 },
1758 { 2010 {
1759 "defaultMessage": "!!!Drop your image, or click here", 2011 "defaultMessage": "!!!Drop your image, or click here",
1760 "end": { 2012 "end": {
1761 "column": 3, 2013 "column": 3,
1762 "line": 97 2014 "line": 99
1763 }, 2015 },
1764 "file": "src/components/settings/services/EditServiceForm.js", 2016 "file": "src/components/settings/services/EditServiceForm.js",
1765 "id": "settings.service.form.iconUpload", 2017 "id": "settings.service.form.iconUpload",
1766 "start": { 2018 "start": {
1767 "column": 14, 2019 "column": 14,
1768 "line": 94 2020 "line": 96
1769 } 2021 }
1770 }, 2022 },
1771 { 2023 {
1772 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 2024 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
1773 "end": { 2025 "end": {
1774 "column": 3, 2026 "column": 3,
1775 "line": 101 2027 "line": 103
1776 }, 2028 },
1777 "file": "src/components/settings/services/EditServiceForm.js", 2029 "file": "src/components/settings/services/EditServiceForm.js",
1778 "id": "settings.service.form.proxy.headline", 2030 "id": "settings.service.form.proxy.headline",
1779 "start": { 2031 "start": {
1780 "column": 17, 2032 "column": 17,
1781 "line": 98 2033 "line": 100
1782 } 2034 }
1783 }, 2035 },
1784 { 2036 {
1785 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", 2037 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.",
1786 "end": { 2038 "end": {
1787 "column": 3, 2039 "column": 3,
1788 "line": 105 2040 "line": 107
1789 }, 2041 },
1790 "file": "src/components/settings/services/EditServiceForm.js", 2042 "file": "src/components/settings/services/EditServiceForm.js",
1791 "id": "settings.service.form.proxy.restartInfo", 2043 "id": "settings.service.form.proxy.restartInfo",
1792 "start": { 2044 "start": {
1793 "column": 20, 2045 "column": 20,
1794 "line": 102 2046 "line": 104
1795 } 2047 }
1796 }, 2048 },
1797 { 2049 {
1798 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", 2050 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.",
1799 "end": { 2051 "end": {
1800 "column": 3, 2052 "column": 3,
1801 "line": 109 2053 "line": 111
1802 }, 2054 },
1803 "file": "src/components/settings/services/EditServiceForm.js", 2055 "file": "src/components/settings/services/EditServiceForm.js",
1804 "id": "settings.service.form.proxy.info", 2056 "id": "settings.service.form.proxy.info",
1805 "start": { 2057 "start": {
1806 "column": 13, 2058 "column": 13,
1807 "line": 106 2059 "line": 108
1808 } 2060 }
1809 } 2061 }
1810 ], 2062 ],
@@ -1917,117 +2169,117 @@
1917 "defaultMessage": "!!!Your services", 2169 "defaultMessage": "!!!Your services",
1918 "end": { 2170 "end": {
1919 "column": 3, 2171 "column": 3,
1920 "line": 17 2172 "line": 18
1921 }, 2173 },
1922 "file": "src/components/settings/services/ServicesDashboard.js", 2174 "file": "src/components/settings/services/ServicesDashboard.js",
1923 "id": "settings.services.headline", 2175 "id": "settings.services.headline",
1924 "start": { 2176 "start": {
1925 "column": 12, 2177 "column": 12,
1926 "line": 14 2178 "line": 15
1927 } 2179 }
1928 }, 2180 },
1929 { 2181 {
1930 "defaultMessage": "!!!Search service", 2182 "defaultMessage": "!!!Search service",
1931 "end": { 2183 "end": {
1932 "column": 3, 2184 "column": 3,
1933 "line": 21 2185 "line": 22
1934 }, 2186 },
1935 "file": "src/components/settings/services/ServicesDashboard.js", 2187 "file": "src/components/settings/services/ServicesDashboard.js",
1936 "id": "settings.searchService", 2188 "id": "settings.searchService",
1937 "start": { 2189 "start": {
1938 "column": 17, 2190 "column": 17,
1939 "line": 18 2191 "line": 19
1940 } 2192 }
1941 }, 2193 },
1942 { 2194 {
1943 "defaultMessage": "!!!You haven't added any services yet.", 2195 "defaultMessage": "!!!You haven't added any services yet.",
1944 "end": { 2196 "end": {
1945 "column": 3, 2197 "column": 3,
1946 "line": 25 2198 "line": 26
1947 }, 2199 },
1948 "file": "src/components/settings/services/ServicesDashboard.js", 2200 "file": "src/components/settings/services/ServicesDashboard.js",
1949 "id": "settings.services.noServicesAdded", 2201 "id": "settings.services.noServicesAdded",
1950 "start": { 2202 "start": {
1951 "column": 19, 2203 "column": 19,
1952 "line": 22 2204 "line": 23
1953 } 2205 }
1954 }, 2206 },
1955 { 2207 {
1956 "defaultMessage": "!!!Sorry, but no service matched your search term.", 2208 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1957 "end": { 2209 "end": {
1958 "column": 3, 2210 "column": 3,
1959 "line": 29 2211 "line": 30
1960 }, 2212 },
1961 "file": "src/components/settings/services/ServicesDashboard.js", 2213 "file": "src/components/settings/services/ServicesDashboard.js",
1962 "id": "settings.recipes.nothingFound", 2214 "id": "settings.recipes.nothingFound",
1963 "start": { 2215 "start": {
1964 "column": 18, 2216 "column": 18,
1965 "line": 26 2217 "line": 27
1966 } 2218 }
1967 }, 2219 },
1968 { 2220 {
1969 "defaultMessage": "!!!Discover services", 2221 "defaultMessage": "!!!Discover services",
1970 "end": { 2222 "end": {
1971 "column": 3, 2223 "column": 3,
1972 "line": 33 2224 "line": 34
1973 }, 2225 },
1974 "file": "src/components/settings/services/ServicesDashboard.js", 2226 "file": "src/components/settings/services/ServicesDashboard.js",
1975 "id": "settings.services.discoverServices", 2227 "id": "settings.services.discoverServices",
1976 "start": { 2228 "start": {
1977 "column": 20, 2229 "column": 20,
1978 "line": 30 2230 "line": 31
1979 } 2231 }
1980 }, 2232 },
1981 { 2233 {
1982 "defaultMessage": "!!!Could not load your services", 2234 "defaultMessage": "!!!Could not load your services",
1983 "end": { 2235 "end": {
1984 "column": 3, 2236 "column": 3,
1985 "line": 37 2237 "line": 38
1986 }, 2238 },
1987 "file": "src/components/settings/services/ServicesDashboard.js", 2239 "file": "src/components/settings/services/ServicesDashboard.js",
1988 "id": "settings.services.servicesRequestFailed", 2240 "id": "settings.services.servicesRequestFailed",
1989 "start": { 2241 "start": {
1990 "column": 25, 2242 "column": 25,
1991 "line": 34 2243 "line": 35
1992 } 2244 }
1993 }, 2245 },
1994 { 2246 {
1995 "defaultMessage": "!!!Try again", 2247 "defaultMessage": "!!!Try again",
1996 "end": { 2248 "end": {
1997 "column": 3, 2249 "column": 3,
1998 "line": 41 2250 "line": 42
1999 }, 2251 },
2000 "file": "src/components/settings/services/ServicesDashboard.js", 2252 "file": "src/components/settings/services/ServicesDashboard.js",
2001 "id": "settings.account.tryReloadServices", 2253 "id": "settings.account.tryReloadServices",
2002 "start": { 2254 "start": {
2003 "column": 21, 2255 "column": 21,
2004 "line": 38 2256 "line": 39
2005 } 2257 }
2006 }, 2258 },
2007 { 2259 {
2008 "defaultMessage": "!!!Your changes have been saved", 2260 "defaultMessage": "!!!Your changes have been saved",
2009 "end": { 2261 "end": {
2010 "column": 3, 2262 "column": 3,
2011 "line": 45 2263 "line": 46
2012 }, 2264 },
2013 "file": "src/components/settings/services/ServicesDashboard.js", 2265 "file": "src/components/settings/services/ServicesDashboard.js",
2014 "id": "settings.services.updatedInfo", 2266 "id": "settings.services.updatedInfo",
2015 "start": { 2267 "start": {
2016 "column": 15, 2268 "column": 15,
2017 "line": 42 2269 "line": 43
2018 } 2270 }
2019 }, 2271 },
2020 { 2272 {
2021 "defaultMessage": "!!!Service has been deleted", 2273 "defaultMessage": "!!!Service has been deleted",
2022 "end": { 2274 "end": {
2023 "column": 3, 2275 "column": 3,
2024 "line": 49 2276 "line": 50
2025 }, 2277 },
2026 "file": "src/components/settings/services/ServicesDashboard.js", 2278 "file": "src/components/settings/services/ServicesDashboard.js",
2027 "id": "settings.services.deletedInfo", 2279 "id": "settings.services.deletedInfo",
2028 "start": { 2280 "start": {
2029 "column": 15, 2281 "column": 15,
2030 "line": 46 2282 "line": 47
2031 } 2283 }
2032 } 2284 }
2033 ], 2285 ],
@@ -2441,220 +2693,339 @@
2441 { 2693 {
2442 "descriptors": [ 2694 "descriptors": [
2443 { 2695 {
2444 "defaultMessage": "!!!Support the development of Franz", 2696 "defaultMessage": "!!!Choose your plan",
2445 "end": { 2697 "end": {
2446 "column": 3, 2698 "column": 3,
2699 "line": 16
2700 },
2701 "file": "src/components/subscription/SubscriptionForm.js",
2702 "id": "subscription.cta.choosePlan",
2703 "start": {
2704 "column": 21,
2705 "line": 13
2706 }
2707 },
2708 {
2709 "defaultMessage": "!!!Upgrade your account and get the full Franz experience",
2710 "end": {
2711 "column": 3,
2712 "line": 20
2713 },
2714 "file": "src/components/subscription/SubscriptionForm.js",
2715 "id": "settings.account.headlineUpgradeAccount",
2716 "start": {
2717 "column": 18,
2447 "line": 17 2718 "line": 17
2719 }
2720 },
2721 {
2722 "defaultMessage": "!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
2723 "end": {
2724 "column": 3,
2725 "line": 24
2726 },
2727 "file": "src/components/subscription/SubscriptionForm.js",
2728 "id": "subscription.teaser.intro",
2729 "start": {
2730 "column": 14,
2731 "line": 21
2732 }
2733 },
2734 {
2735 "defaultMessage": "!!!Paid Franz Plans include:",
2736 "end": {
2737 "column": 3,
2738 "line": 28
2448 }, 2739 },
2449 "file": "src/components/subscription/SubscriptionForm.js", 2740 "file": "src/components/subscription/SubscriptionForm.js",
2450 "id": "subscription.submit.label", 2741 "id": "subscription.teaser.includedFeatures",
2742 "start": {
2743 "column": 20,
2744 "line": 25
2745 }
2746 }
2747 ],
2748 "path": "src/components/subscription/SubscriptionForm.json"
2749 },
2750 {
2751 "descriptors": [
2752 {
2753 "defaultMessage": "!!!Cancel",
2754 "end": {
2755 "column": 3,
2756 "line": 14
2757 },
2758 "file": "src/components/subscription/SubscriptionPopup.js",
2759 "id": "subscriptionPopup.buttonCancel",
2760 "start": {
2761 "column": 16,
2762 "line": 11
2763 }
2764 },
2765 {
2766 "defaultMessage": "!!!Done",
2767 "end": {
2768 "column": 3,
2769 "line": 18
2770 },
2771 "file": "src/components/subscription/SubscriptionPopup.js",
2772 "id": "subscriptionPopup.buttonDone",
2773 "start": {
2774 "column": 14,
2775 "line": 15
2776 }
2777 }
2778 ],
2779 "path": "src/components/subscription/SubscriptionPopup.json"
2780 },
2781 {
2782 "descriptors": [
2783 {
2784 "defaultMessage": "!!!Yes, start the free Franz Professional trial",
2785 "end": {
2786 "column": 3,
2787 "line": 17
2788 },
2789 "file": "src/components/subscription/TrialForm.js",
2790 "id": "subscription.cta.activateTrial",
2451 "start": { 2791 "start": {
2452 "column": 21, 2792 "column": 21,
2453 "line": 14 2793 "line": 14
2454 } 2794 }
2455 }, 2795 },
2456 { 2796 {
2457 "defaultMessage": "!!!Could not initialize payment form", 2797 "defaultMessage": "!!!See all options",
2458 "end": { 2798 "end": {
2459 "column": 3, 2799 "column": 3,
2460 "line": 21 2800 "line": 21
2461 }, 2801 },
2462 "file": "src/components/subscription/SubscriptionForm.js", 2802 "file": "src/components/subscription/TrialForm.js",
2463 "id": "subscription.paymentSessionError", 2803 "id": "subscription.cta.allOptions",
2464 "start": { 2804 "start": {
2465 "column": 23, 2805 "column": 20,
2466 "line": 18 2806 "line": 18
2467 } 2807 }
2468 }, 2808 },
2469 { 2809 {
2470 "defaultMessage": "!!!free", 2810 "defaultMessage": "!!!Get the free 14 day Franz Professional Trial",
2471 "end": { 2811 "end": {
2472 "column": 3, 2812 "column": 3,
2473 "line": 25 2813 "line": 25
2474 }, 2814 },
2475 "file": "src/components/subscription/SubscriptionForm.js", 2815 "file": "src/components/subscription/TrialForm.js",
2476 "id": "subscription.type.free", 2816 "id": "settings.account.headlineTrialUpgrade",
2477 "start": { 2817 "start": {
2478 "column": 12, 2818 "column": 18,
2479 "line": 22 2819 "line": 22
2480 } 2820 }
2481 }, 2821 },
2482 { 2822 {
2483 "defaultMessage": "!!!month", 2823 "defaultMessage": "!!!The Franz Professional Plan includes:",
2484 "end": { 2824 "end": {
2485 "column": 3, 2825 "column": 3,
2486 "line": 29 2826 "line": 29
2487 }, 2827 },
2488 "file": "src/components/subscription/SubscriptionForm.js", 2828 "file": "src/components/subscription/TrialForm.js",
2489 "id": "subscription.type.month", 2829 "id": "subscription.includedProFeatures",
2490 "start": { 2830 "start": {
2491 "column": 15, 2831 "column": 20,
2492 "line": 26 2832 "line": 26
2493 } 2833 }
2494 }, 2834 },
2495 { 2835 {
2496 "defaultMessage": "!!!year", 2836 "defaultMessage": "!!!No strings attached",
2497 "end": { 2837 "end": {
2498 "column": 3, 2838 "column": 3,
2499 "line": 33 2839 "line": 33
2500 }, 2840 },
2501 "file": "src/components/subscription/SubscriptionForm.js", 2841 "file": "src/components/subscription/TrialForm.js",
2502 "id": "subscription.type.year", 2842 "id": "pricing.trial.terms.headline",
2503 "start": { 2843 "start": {
2504 "column": 14, 2844 "column": 29,
2505 "line": 30 2845 "line": 30
2506 } 2846 }
2507 }, 2847 },
2508 { 2848 {
2509 "defaultMessage": "!!!The Franz Premium Supporter Account includes", 2849 "defaultMessage": "!!!No credit card required",
2510 "end": { 2850 "end": {
2511 "column": 3, 2851 "column": 3,
2512 "line": 37 2852 "line": 37
2513 }, 2853 },
2514 "file": "src/components/subscription/SubscriptionForm.js", 2854 "file": "src/components/subscription/TrialForm.js",
2515 "id": "subscription.includedFeatures", 2855 "id": "pricing.trial.terms.noCreditCard",
2516 "start": { 2856 "start": {
2517 "column": 20, 2857 "column": 16,
2518 "line": 34 2858 "line": 34
2519 } 2859 }
2520 }, 2860 },
2521 { 2861 {
2522 "defaultMessage": "!!!Add on-premise/hosted services like Mattermost", 2862 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
2523 "end": { 2863 "end": {
2524 "column": 3, 2864 "column": 3,
2525 "line": 41 2865 "line": 41
2526 }, 2866 },
2527 "file": "src/components/subscription/SubscriptionForm.js", 2867 "file": "src/components/subscription/TrialForm.js",
2528 "id": "subscription.features.onpremise.mattermost", 2868 "id": "pricing.trial.terms.automaticTrialEnd",
2529 "start": { 2869 "start": {
2530 "column": 13, 2870 "column": 21,
2531 "line": 38 2871 "line": 38
2532 } 2872 }
2533 }, 2873 }
2874 ],
2875 "path": "src/components/subscription/TrialForm.json"
2876 },
2877 {
2878 "descriptors": [
2534 { 2879 {
2535 "defaultMessage": "!!!No app delays & nagging to upgrade license", 2880 "defaultMessage": "!!!Your trial was successfully activated. Happy messaging!",
2536 "end": { 2881 "end": {
2537 "column": 3, 2882 "column": 3,
2538 "line": 45 2883 "line": 14
2539 }, 2884 },
2540 "file": "src/components/subscription/SubscriptionForm.js", 2885 "file": "src/components/TrialActivationInfoBar.js",
2541 "id": "subscription.features.noInterruptions", 2886 "id": "infobar.trialActivated",
2542 "start": { 2887 "start": {
2543 "column": 19, 2888 "column": 11,
2544 "line": 42 2889 "line": 11
2545 } 2890 }
2546 }, 2891 }
2892 ],
2893 "path": "src/components/TrialActivationInfoBar.json"
2894 },
2895 {
2896 "descriptors": [
2547 { 2897 {
2548 "defaultMessage": "!!!Proxy support for services", 2898 "defaultMessage": "!!!Add unlimited services",
2549 "end": { 2899 "end": {
2550 "column": 3, 2900 "column": 3,
2551 "line": 49 2901 "line": 11
2552 }, 2902 },
2553 "file": "src/components/subscription/SubscriptionForm.js", 2903 "file": "src/components/ui/FeatureList.js",
2554 "id": "subscription.features.proxy", 2904 "id": "pricing.features.unlimitedServices",
2555 "start": { 2905 "start": {
2556 "column": 9, 2906 "column": 21,
2557 "line": 46 2907 "line": 8
2558 } 2908 }
2559 }, 2909 },
2560 { 2910 {
2561 "defaultMessage": "!!!Support for Spellchecker", 2911 "defaultMessage": "!!!Spellchecker support",
2562 "end": { 2912 "end": {
2563 "column": 3, 2913 "column": 3,
2564 "line": 53 2914 "line": 15
2565 }, 2915 },
2566 "file": "src/components/subscription/SubscriptionForm.js", 2916 "file": "src/components/ui/FeatureList.js",
2567 "id": "subscription.features.spellchecker", 2917 "id": "pricing.features.spellchecker",
2568 "start": { 2918 "start": {
2569 "column": 16, 2919 "column": 16,
2570 "line": 50 2920 "line": 12
2571 } 2921 }
2572 }, 2922 },
2573 { 2923 {
2574 "defaultMessage": "!!!Organize your services in workspaces", 2924 "defaultMessage": "!!!Workspaces",
2575 "end": { 2925 "end": {
2576 "column": 3, 2926 "column": 3,
2577 "line": 57 2927 "line": 19
2578 }, 2928 },
2579 "file": "src/components/subscription/SubscriptionForm.js", 2929 "file": "src/components/ui/FeatureList.js",
2580 "id": "subscription.features.workspaces", 2930 "id": "pricing.features.workspaces",
2581 "start": { 2931 "start": {
2582 "column": 14, 2932 "column": 14,
2583 "line": 54 2933 "line": 16
2584 } 2934 }
2585 }, 2935 },
2586 { 2936 {
2587 "defaultMessage": "!!!No ads, ever!", 2937 "defaultMessage": "!!!Add Custom Websites",
2588 "end": { 2938 "end": {
2589 "column": 3, 2939 "column": 3,
2590 "line": 61 2940 "line": 23
2591 }, 2941 },
2592 "file": "src/components/subscription/SubscriptionForm.js", 2942 "file": "src/components/ui/FeatureList.js",
2593 "id": "subscription.features.ads", 2943 "id": "pricing.features.customWebsites",
2594 "start": { 2944 "start": {
2595 "column": 7, 2945 "column": 18,
2596 "line": 58 2946 "line": 20
2597 } 2947 }
2598 }, 2948 },
2599 { 2949 {
2600 "defaultMessage": "!!!coming soon", 2950 "defaultMessage": "!!!On-premise & other Hosted Services",
2601 "end": { 2951 "end": {
2602 "column": 3, 2952 "column": 3,
2603 "line": 65 2953 "line": 27
2604 }, 2954 },
2605 "file": "src/components/subscription/SubscriptionForm.js", 2955 "file": "src/components/ui/FeatureList.js",
2606 "id": "subscription.features.comingSoon", 2956 "id": "pricing.features.onPremise",
2607 "start": { 2957 "start": {
2608 "column": 14, 2958 "column": 13,
2609 "line": 62 2959 "line": 24
2610 } 2960 }
2611 }, 2961 },
2612 { 2962 {
2613 "defaultMessage": "!!!EU residents: local sales tax may apply", 2963 "defaultMessage": "!!!Install 3rd party services",
2614 "end": { 2964 "end": {
2615 "column": 3, 2965 "column": 3,
2616 "line": 69 2966 "line": 31
2617 }, 2967 },
2618 "file": "src/components/subscription/SubscriptionForm.js", 2968 "file": "src/components/ui/FeatureList.js",
2619 "id": "subscription.euTaxInfo", 2969 "id": "pricing.features.thirdPartyServices",
2620 "start": { 2970 "start": {
2621 "column": 13, 2971 "column": 22,
2622 "line": 66 2972 "line": 28
2623 } 2973 }
2624 } 2974 },
2625 ],
2626 "path": "src/components/subscription/SubscriptionForm.json"
2627 },
2628 {
2629 "descriptors": [
2630 { 2975 {
2631 "defaultMessage": "!!!Cancel", 2976 "defaultMessage": "!!!Service Proxies",
2632 "end": { 2977 "end": {
2633 "column": 3, 2978 "column": 3,
2634 "line": 14 2979 "line": 35
2635 }, 2980 },
2636 "file": "src/components/subscription/SubscriptionPopup.js", 2981 "file": "src/components/ui/FeatureList.js",
2637 "id": "subscriptionPopup.buttonCancel", 2982 "id": "pricing.features.serviceProxies",
2638 "start": { 2983 "start": {
2639 "column": 16, 2984 "column": 18,
2640 "line": 11 2985 "line": 32
2641 } 2986 }
2642 }, 2987 },
2643 { 2988 {
2644 "defaultMessage": "!!!Done", 2989 "defaultMessage": "!!!Team Management",
2645 "end": { 2990 "end": {
2646 "column": 3, 2991 "column": 3,
2647 "line": 18 2992 "line": 39
2648 }, 2993 },
2649 "file": "src/components/subscription/SubscriptionPopup.js", 2994 "file": "src/components/ui/FeatureList.js",
2650 "id": "subscriptionPopup.buttonDone", 2995 "id": "pricing.features.teamManagement",
2651 "start": { 2996 "start": {
2652 "column": 14, 2997 "column": 18,
2653 "line": 15 2998 "line": 36
2999 }
3000 },
3001 {
3002 "defaultMessage": "!!!No Waiting Screens",
3003 "end": {
3004 "column": 3,
3005 "line": 43
3006 },
3007 "file": "src/components/ui/FeatureList.js",
3008 "id": "pricing.features.appDelays",
3009 "start": {
3010 "column": 13,
3011 "line": 40
3012 }
3013 },
3014 {
3015 "defaultMessage": "!!!Forever ad-free",
3016 "end": {
3017 "column": 3,
3018 "line": 47
3019 },
3020 "file": "src/components/ui/FeatureList.js",
3021 "id": "pricing.features.adFree",
3022 "start": {
3023 "column": 10,
3024 "line": 44
2654 } 3025 }
2655 } 3026 }
2656 ], 3027 ],
2657 "path": "src/components/subscription/SubscriptionPopup.json" 3028 "path": "src/components/ui/FeatureList.json"
2658 }, 3029 },
2659 { 3030 {
2660 "descriptors": [ 3031 "descriptors": [
@@ -3230,39 +3601,65 @@
3230 "defaultMessage": "!!!Please purchase license to skip waiting", 3601 "defaultMessage": "!!!Please purchase license to skip waiting",
3231 "end": { 3602 "end": {
3232 "column": 3, 3603 "column": 3,
3233 "line": 18 3604 "line": 20
3234 }, 3605 },
3235 "file": "src/features/delayApp/Component.js", 3606 "file": "src/features/delayApp/Component.js",
3236 "id": "feature.delayApp.headline", 3607 "id": "feature.delayApp.headline",
3237 "start": { 3608 "start": {
3238 "column": 12, 3609 "column": 12,
3239 "line": 15 3610 "line": 17
3611 }
3612 },
3613 {
3614 "defaultMessage": "!!!Get the free Franz Professional 14 day trial and skip the line",
3615 "end": {
3616 "column": 3,
3617 "line": 24
3618 },
3619 "file": "src/features/delayApp/Component.js",
3620 "id": "feature.delayApp.trial.headline",
3621 "start": {
3622 "column": 17,
3623 "line": 21
3240 } 3624 }
3241 }, 3625 },
3242 { 3626 {
3243 "defaultMessage": "!!!Get a Franz Supporter License", 3627 "defaultMessage": "!!!Get a Franz Supporter License",
3244 "end": { 3628 "end": {
3245 "column": 3, 3629 "column": 3,
3246 "line": 22 3630 "line": 28
3247 }, 3631 },
3248 "file": "src/features/delayApp/Component.js", 3632 "file": "src/features/delayApp/Component.js",
3249 "id": "feature.delayApp.action", 3633 "id": "feature.delayApp.upgrade.action",
3250 "start": { 3634 "start": {
3251 "column": 10, 3635 "column": 10,
3252 "line": 19 3636 "line": 25
3637 }
3638 },
3639 {
3640 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
3641 "end": {
3642 "column": 3,
3643 "line": 32
3644 },
3645 "file": "src/features/delayApp/Component.js",
3646 "id": "feature.delayApp.trial.action",
3647 "start": {
3648 "column": 15,
3649 "line": 29
3253 } 3650 }
3254 }, 3651 },
3255 { 3652 {
3256 "defaultMessage": "!!!Franz will continue in {seconds} seconds.", 3653 "defaultMessage": "!!!Franz will continue in {seconds} seconds.",
3257 "end": { 3654 "end": {
3258 "column": 3, 3655 "column": 3,
3259 "line": 26 3656 "line": 36
3260 }, 3657 },
3261 "file": "src/features/delayApp/Component.js", 3658 "file": "src/features/delayApp/Component.js",
3262 "id": "feature.delayApp.text", 3659 "id": "feature.delayApp.text",
3263 "start": { 3660 "start": {
3264 "column": 8, 3661 "column": 8,
3265 "line": 23 3662 "line": 33
3266 } 3663 }
3267 } 3664 }
3268 ], 3665 ],
@@ -3271,94 +3668,143 @@
3271 { 3668 {
3272 "descriptors": [ 3669 "descriptors": [
3273 { 3670 {
3274 "defaultMessage": "!!!Franz is better together!", 3671 "defaultMessage": "!!!Changes in Franz {version}",
3672 "end": {
3673 "column": 3,
3674 "line": 23
3675 },
3676 "file": "src/features/serviceLimit/components/AnnouncementScreen.js",
3677 "id": "feature.announcements.changelog.headline",
3678 "start": {
3679 "column": 12,
3680 "line": 20
3681 }
3682 }
3683 ],
3684 "path": "src/features/serviceLimit/components/AnnouncementScreen.json"
3685 },
3686 {
3687 "descriptors": [
3688 {
3689 "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.",
3690 "end": {
3691 "column": 3,
3692 "line": 14
3693 },
3694 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3695 "id": "feature.serviceLimit.limitReached",
3696 "start": {
3697 "column": 16,
3698 "line": 11
3699 }
3700 },
3701 {
3702 "defaultMessage": "!!!Upgrade account",
3275 "end": { 3703 "end": {
3276 "column": 3, 3704 "column": 3,
3277 "line": 18 3705 "line": 18
3278 }, 3706 },
3707 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3708 "id": "premiumFeature.button.upgradeAccount",
3709 "start": {
3710 "column": 10,
3711 "line": 15
3712 }
3713 }
3714 ],
3715 "path": "src/features/serviceLimit/components/LimitReachedInfobox.json"
3716 },
3717 {
3718 "descriptors": [
3719 {
3720 "defaultMessage": "!!!Franz is better together!",
3721 "end": {
3722 "column": 3,
3723 "line": 19
3724 },
3279 "file": "src/features/shareFranz/Component.js", 3725 "file": "src/features/shareFranz/Component.js",
3280 "id": "feature.shareFranz.headline", 3726 "id": "feature.shareFranz.headline",
3281 "start": { 3727 "start": {
3282 "column": 12, 3728 "column": 12,
3283 "line": 15 3729 "line": 16
3284 } 3730 }
3285 }, 3731 },
3286 { 3732 {
3287 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.", 3733 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
3288 "end": { 3734 "end": {
3289 "column": 3, 3735 "column": 3,
3290 "line": 22 3736 "line": 23
3291 }, 3737 },
3292 "file": "src/features/shareFranz/Component.js", 3738 "file": "src/features/shareFranz/Component.js",
3293 "id": "feature.shareFranz.text", 3739 "id": "feature.shareFranz.text",
3294 "start": { 3740 "start": {
3295 "column": 8, 3741 "column": 8,
3296 "line": 19 3742 "line": 20
3297 } 3743 }
3298 }, 3744 },
3299 { 3745 {
3300 "defaultMessage": "!!!Share as email", 3746 "defaultMessage": "!!!Share as email",
3301 "end": { 3747 "end": {
3302 "column": 3, 3748 "column": 3,
3303 "line": 26 3749 "line": 27
3304 }, 3750 },
3305 "file": "src/features/shareFranz/Component.js", 3751 "file": "src/features/shareFranz/Component.js",
3306 "id": "feature.shareFranz.action.email", 3752 "id": "feature.shareFranz.action.email",
3307 "start": { 3753 "start": {
3308 "column": 16, 3754 "column": 16,
3309 "line": 23 3755 "line": 24
3310 } 3756 }
3311 }, 3757 },
3312 { 3758 {
3313 "defaultMessage": "!!!Share on Facebook", 3759 "defaultMessage": "!!!Share on Facebook",
3314 "end": { 3760 "end": {
3315 "column": 3, 3761 "column": 3,
3316 "line": 30 3762 "line": 31
3317 }, 3763 },
3318 "file": "src/features/shareFranz/Component.js", 3764 "file": "src/features/shareFranz/Component.js",
3319 "id": "feature.shareFranz.action.facebook", 3765 "id": "feature.shareFranz.action.facebook",
3320 "start": { 3766 "start": {
3321 "column": 19, 3767 "column": 19,
3322 "line": 27 3768 "line": 28
3323 } 3769 }
3324 }, 3770 },
3325 { 3771 {
3326 "defaultMessage": "!!!Share on Twitter", 3772 "defaultMessage": "!!!Share on Twitter",
3327 "end": { 3773 "end": {
3328 "column": 3, 3774 "column": 3,
3329 "line": 34 3775 "line": 35
3330 }, 3776 },
3331 "file": "src/features/shareFranz/Component.js", 3777 "file": "src/features/shareFranz/Component.js",
3332 "id": "feature.shareFranz.action.twitter", 3778 "id": "feature.shareFranz.action.twitter",
3333 "start": { 3779 "start": {
3334 "column": 18, 3780 "column": 18,
3335 "line": 31 3781 "line": 32
3336 } 3782 }
3337 }, 3783 },
3338 { 3784 {
3339 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", 3785 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
3340 "end": { 3786 "end": {
3341 "column": 3, 3787 "column": 3,
3342 "line": 38 3788 "line": 39
3343 }, 3789 },
3344 "file": "src/features/shareFranz/Component.js", 3790 "file": "src/features/shareFranz/Component.js",
3345 "id": "feature.shareFranz.shareText.email", 3791 "id": "feature.shareFranz.shareText.email",
3346 "start": { 3792 "start": {
3347 "column": 18, 3793 "column": 18,
3348 "line": 35 3794 "line": 36
3349 } 3795 }
3350 }, 3796 },
3351 { 3797 {
3352 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger", 3798 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
3353 "end": { 3799 "end": {
3354 "column": 3, 3800 "column": 3,
3355 "line": 42 3801 "line": 43
3356 }, 3802 },
3357 "file": "src/features/shareFranz/Component.js", 3803 "file": "src/features/shareFranz/Component.js",
3358 "id": "feature.shareFranz.shareText.twitter", 3804 "id": "feature.shareFranz.shareText.twitter",
3359 "start": { 3805 "start": {
3360 "column": 20, 3806 "column": 20,
3361 "line": 39 3807 "line": 40
3362 } 3808 }
3363 } 3809 }
3364 ], 3810 ],
@@ -3761,6 +4207,146 @@
3761 { 4207 {
3762 "descriptors": [ 4208 "descriptors": [
3763 { 4209 {
4210 "defaultMessage": "!!!Franz Professional",
4211 "end": {
4212 "column": 3,
4213 "line": 8
4214 },
4215 "file": "src/helpers/plan-helpers.js",
4216 "id": "pricing.plan.pro",
4217 "start": {
4218 "column": 15,
4219 "line": 5
4220 }
4221 },
4222 {
4223 "defaultMessage": "!!!Franz Personal",
4224 "end": {
4225 "column": 3,
4226 "line": 12
4227 },
4228 "file": "src/helpers/plan-helpers.js",
4229 "id": "pricing.plan.personal",
4230 "start": {
4231 "column": 20,
4232 "line": 9
4233 }
4234 },
4235 {
4236 "defaultMessage": "!!!Franz Free",
4237 "end": {
4238 "column": 3,
4239 "line": 16
4240 },
4241 "file": "src/helpers/plan-helpers.js",
4242 "id": "pricing.plan.free",
4243 "start": {
4244 "column": 16,
4245 "line": 13
4246 }
4247 },
4248 {
4249 "defaultMessage": "!!!Franz Premium",
4250 "end": {
4251 "column": 3,
4252 "line": 20
4253 },
4254 "file": "src/helpers/plan-helpers.js",
4255 "id": "pricing.plan.legacy",
4256 "start": {
4257 "column": 18,
4258 "line": 17
4259 }
4260 }
4261 ],
4262 "path": "src/helpers/plan-helpers.json"
4263 },
4264 {
4265 "descriptors": [
4266 {
4267 "defaultMessage": "!!!Franz Professional Yearly",
4268 "end": {
4269 "column": 3,
4270 "line": 8
4271 },
4272 "file": "src/helpers/pricing-helpers.js",
4273 "id": "pricing.plan.pro-yearly",
4274 "start": {
4275 "column": 22,
4276 "line": 5
4277 }
4278 },
4279 {
4280 "defaultMessage": "!!!Franz Professional Monthly",
4281 "end": {
4282 "column": 3,
4283 "line": 12
4284 },
4285 "file": "src/helpers/pricing-helpers.js",
4286 "id": "pricing.plan.pro-monthly",
4287 "start": {
4288 "column": 23,
4289 "line": 9
4290 }
4291 },
4292 {
4293 "defaultMessage": "!!!Franz Personal Yearly",
4294 "end": {
4295 "column": 3,
4296 "line": 16
4297 },
4298 "file": "src/helpers/pricing-helpers.js",
4299 "id": "pricing.plan.personal-yearly",
4300 "start": {
4301 "column": 27,
4302 "line": 13
4303 }
4304 },
4305 {
4306 "defaultMessage": "!!!Franz Personal Monthly",
4307 "end": {
4308 "column": 3,
4309 "line": 20
4310 },
4311 "file": "src/helpers/pricing-helpers.js",
4312 "id": "pricing.plan.personal-monthly",
4313 "start": {
4314 "column": 28,
4315 "line": 17
4316 }
4317 },
4318 {
4319 "defaultMessage": "!!!Franz Free",
4320 "end": {
4321 "column": 3,
4322 "line": 24
4323 },
4324 "file": "src/helpers/pricing-helpers.js",
4325 "id": "pricing.plan.free",
4326 "start": {
4327 "column": 16,
4328 "line": 21
4329 }
4330 },
4331 {
4332 "defaultMessage": "!!!Franz Premium",
4333 "end": {
4334 "column": 3,
4335 "line": 28
4336 },
4337 "file": "src/helpers/pricing-helpers.js",
4338 "id": "pricing.plan.legacy",
4339 "start": {
4340 "column": 18,
4341 "line": 25
4342 }
4343 }
4344 ],
4345 "path": "src/helpers/pricing-helpers.json"
4346 },
4347 {
4348 "descriptors": [
4349 {
3764 "defaultMessage": "!!!Field is required", 4350 "defaultMessage": "!!!Field is required",
3765 "end": { 4351 "end": {
3766 "column": 3, 4352 "column": 3,
@@ -3917,702 +4503,793 @@
3917 "defaultMessage": "!!!Edit", 4503 "defaultMessage": "!!!Edit",
3918 "end": { 4504 "end": {
3919 "column": 3, 4505 "column": 3,
3920 "line": 18 4506 "line": 21
3921 }, 4507 },
3922 "file": "src/lib/Menu.js", 4508 "file": "src/lib/Menu.js",
3923 "id": "menu.edit", 4509 "id": "menu.edit",
3924 "start": { 4510 "start": {
3925 "column": 8, 4511 "column": 8,
3926 "line": 15 4512 "line": 18
3927 } 4513 }
3928 }, 4514 },
3929 { 4515 {
3930 "defaultMessage": "!!!Undo", 4516 "defaultMessage": "!!!Undo",
3931 "end": { 4517 "end": {
3932 "column": 3, 4518 "column": 3,
3933 "line": 22 4519 "line": 25
3934 }, 4520 },
3935 "file": "src/lib/Menu.js", 4521 "file": "src/lib/Menu.js",
3936 "id": "menu.edit.undo", 4522 "id": "menu.edit.undo",
3937 "start": { 4523 "start": {
3938 "column": 8, 4524 "column": 8,
3939 "line": 19 4525 "line": 22
3940 } 4526 }
3941 }, 4527 },
3942 { 4528 {
3943 "defaultMessage": "!!!Redo", 4529 "defaultMessage": "!!!Redo",
3944 "end": { 4530 "end": {
3945 "column": 3, 4531 "column": 3,
3946 "line": 26 4532 "line": 29
3947 }, 4533 },
3948 "file": "src/lib/Menu.js", 4534 "file": "src/lib/Menu.js",
3949 "id": "menu.edit.redo", 4535 "id": "menu.edit.redo",
3950 "start": { 4536 "start": {
3951 "column": 8, 4537 "column": 8,
3952 "line": 23 4538 "line": 26
3953 } 4539 }
3954 }, 4540 },
3955 { 4541 {
3956 "defaultMessage": "!!!Cut", 4542 "defaultMessage": "!!!Cut",
3957 "end": { 4543 "end": {
3958 "column": 3, 4544 "column": 3,
3959 "line": 30 4545 "line": 33
3960 }, 4546 },
3961 "file": "src/lib/Menu.js", 4547 "file": "src/lib/Menu.js",
3962 "id": "menu.edit.cut", 4548 "id": "menu.edit.cut",
3963 "start": { 4549 "start": {
3964 "column": 7, 4550 "column": 7,
3965 "line": 27 4551 "line": 30
3966 } 4552 }
3967 }, 4553 },
3968 { 4554 {
3969 "defaultMessage": "!!!Copy", 4555 "defaultMessage": "!!!Copy",
3970 "end": { 4556 "end": {
3971 "column": 3, 4557 "column": 3,
3972 "line": 34 4558 "line": 37
3973 }, 4559 },
3974 "file": "src/lib/Menu.js", 4560 "file": "src/lib/Menu.js",
3975 "id": "menu.edit.copy", 4561 "id": "menu.edit.copy",
3976 "start": { 4562 "start": {
3977 "column": 8, 4563 "column": 8,
3978 "line": 31 4564 "line": 34
3979 } 4565 }
3980 }, 4566 },
3981 { 4567 {
3982 "defaultMessage": "!!!Paste", 4568 "defaultMessage": "!!!Paste",
3983 "end": { 4569 "end": {
3984 "column": 3, 4570 "column": 3,
3985 "line": 38 4571 "line": 41
3986 }, 4572 },
3987 "file": "src/lib/Menu.js", 4573 "file": "src/lib/Menu.js",
3988 "id": "menu.edit.paste", 4574 "id": "menu.edit.paste",
3989 "start": { 4575 "start": {
3990 "column": 9, 4576 "column": 9,
3991 "line": 35 4577 "line": 38
3992 } 4578 }
3993 }, 4579 },
3994 { 4580 {
3995 "defaultMessage": "!!!Paste And Match Style", 4581 "defaultMessage": "!!!Paste And Match Style",
3996 "end": { 4582 "end": {
3997 "column": 3, 4583 "column": 3,
3998 "line": 42 4584 "line": 45
3999 }, 4585 },
4000 "file": "src/lib/Menu.js", 4586 "file": "src/lib/Menu.js",
4001 "id": "menu.edit.pasteAndMatchStyle", 4587 "id": "menu.edit.pasteAndMatchStyle",
4002 "start": { 4588 "start": {
4003 "column": 22, 4589 "column": 22,
4004 "line": 39 4590 "line": 42
4005 } 4591 }
4006 }, 4592 },
4007 { 4593 {
4008 "defaultMessage": "!!!Delete", 4594 "defaultMessage": "!!!Delete",
4009 "end": { 4595 "end": {
4010 "column": 3, 4596 "column": 3,
4011 "line": 46 4597 "line": 49
4012 }, 4598 },
4013 "file": "src/lib/Menu.js", 4599 "file": "src/lib/Menu.js",
4014 "id": "menu.edit.delete", 4600 "id": "menu.edit.delete",
4015 "start": { 4601 "start": {
4016 "column": 10, 4602 "column": 10,
4017 "line": 43 4603 "line": 46
4018 } 4604 }
4019 }, 4605 },
4020 { 4606 {
4021 "defaultMessage": "!!!Select All", 4607 "defaultMessage": "!!!Select All",
4022 "end": { 4608 "end": {
4023 "column": 3, 4609 "column": 3,
4024 "line": 50 4610 "line": 53
4025 }, 4611 },
4026 "file": "src/lib/Menu.js", 4612 "file": "src/lib/Menu.js",
4027 "id": "menu.edit.selectAll", 4613 "id": "menu.edit.selectAll",
4028 "start": { 4614 "start": {
4029 "column": 13, 4615 "column": 13,
4030 "line": 47 4616 "line": 50
4031 } 4617 }
4032 }, 4618 },
4033 { 4619 {
4034 "defaultMessage": "!!!Speech", 4620 "defaultMessage": "!!!Speech",
4035 "end": { 4621 "end": {
4036 "column": 3, 4622 "column": 3,
4037 "line": 54 4623 "line": 57
4038 }, 4624 },
4039 "file": "src/lib/Menu.js", 4625 "file": "src/lib/Menu.js",
4040 "id": "menu.edit.speech", 4626 "id": "menu.edit.speech",
4041 "start": { 4627 "start": {
4042 "column": 10, 4628 "column": 10,
4043 "line": 51 4629 "line": 54
4044 } 4630 }
4045 }, 4631 },
4046 { 4632 {
4047 "defaultMessage": "!!!Start Speaking", 4633 "defaultMessage": "!!!Start Speaking",
4048 "end": { 4634 "end": {
4049 "column": 3, 4635 "column": 3,
4050 "line": 58 4636 "line": 61
4051 }, 4637 },
4052 "file": "src/lib/Menu.js", 4638 "file": "src/lib/Menu.js",
4053 "id": "menu.edit.startSpeaking", 4639 "id": "menu.edit.startSpeaking",
4054 "start": { 4640 "start": {
4055 "column": 17, 4641 "column": 17,
4056 "line": 55 4642 "line": 58
4057 } 4643 }
4058 }, 4644 },
4059 { 4645 {
4060 "defaultMessage": "!!!Stop Speaking", 4646 "defaultMessage": "!!!Stop Speaking",
4061 "end": { 4647 "end": {
4062 "column": 3, 4648 "column": 3,
4063 "line": 62 4649 "line": 65
4064 }, 4650 },
4065 "file": "src/lib/Menu.js", 4651 "file": "src/lib/Menu.js",
4066 "id": "menu.edit.stopSpeaking", 4652 "id": "menu.edit.stopSpeaking",
4067 "start": { 4653 "start": {
4068 "column": 16, 4654 "column": 16,
4069 "line": 59 4655 "line": 62
4070 } 4656 }
4071 }, 4657 },
4072 { 4658 {
4073 "defaultMessage": "!!!Start Dictation", 4659 "defaultMessage": "!!!Start Dictation",
4074 "end": { 4660 "end": {
4075 "column": 3, 4661 "column": 3,
4076 "line": 66 4662 "line": 69
4077 }, 4663 },
4078 "file": "src/lib/Menu.js", 4664 "file": "src/lib/Menu.js",
4079 "id": "menu.edit.startDictation", 4665 "id": "menu.edit.startDictation",
4080 "start": { 4666 "start": {
4081 "column": 18, 4667 "column": 18,
4082 "line": 63 4668 "line": 66
4083 } 4669 }
4084 }, 4670 },
4085 { 4671 {
4086 "defaultMessage": "!!!Emoji & Symbols", 4672 "defaultMessage": "!!!Emoji & Symbols",
4087 "end": { 4673 "end": {
4088 "column": 3, 4674 "column": 3,
4089 "line": 70 4675 "line": 73
4090 }, 4676 },
4091 "file": "src/lib/Menu.js", 4677 "file": "src/lib/Menu.js",
4092 "id": "menu.edit.emojiSymbols", 4678 "id": "menu.edit.emojiSymbols",
4093 "start": { 4679 "start": {
4094 "column": 16, 4680 "column": 16,
4095 "line": 67 4681 "line": 70
4096 } 4682 }
4097 }, 4683 },
4098 { 4684 {
4099 "defaultMessage": "!!!Actual Size", 4685 "defaultMessage": "!!!Actual Size",
4100 "end": { 4686 "end": {
4101 "column": 3, 4687 "column": 3,
4102 "line": 74 4688 "line": 77
4103 }, 4689 },
4104 "file": "src/lib/Menu.js", 4690 "file": "src/lib/Menu.js",
4105 "id": "menu.view.resetZoom", 4691 "id": "menu.view.resetZoom",
4106 "start": { 4692 "start": {
4107 "column": 13, 4693 "column": 13,
4108 "line": 71 4694 "line": 74
4109 } 4695 }
4110 }, 4696 },
4111 { 4697 {
4112 "defaultMessage": "!!!Zoom In", 4698 "defaultMessage": "!!!Zoom In",
4113 "end": { 4699 "end": {
4114 "column": 3, 4700 "column": 3,
4115 "line": 78 4701 "line": 81
4116 }, 4702 },
4117 "file": "src/lib/Menu.js", 4703 "file": "src/lib/Menu.js",
4118 "id": "menu.view.zoomIn", 4704 "id": "menu.view.zoomIn",
4119 "start": { 4705 "start": {
4120 "column": 10, 4706 "column": 10,
4121 "line": 75 4707 "line": 78
4122 } 4708 }
4123 }, 4709 },
4124 { 4710 {
4125 "defaultMessage": "!!!Zoom Out", 4711 "defaultMessage": "!!!Zoom Out",
4126 "end": { 4712 "end": {
4127 "column": 3, 4713 "column": 3,
4128 "line": 82 4714 "line": 85
4129 }, 4715 },
4130 "file": "src/lib/Menu.js", 4716 "file": "src/lib/Menu.js",
4131 "id": "menu.view.zoomOut", 4717 "id": "menu.view.zoomOut",
4132 "start": { 4718 "start": {
4133 "column": 11, 4719 "column": 11,
4134 "line": 79 4720 "line": 82
4135 } 4721 }
4136 }, 4722 },
4137 { 4723 {
4138 "defaultMessage": "!!!Enter Full Screen", 4724 "defaultMessage": "!!!Enter Full Screen",
4139 "end": { 4725 "end": {
4140 "column": 3, 4726 "column": 3,
4141 "line": 86 4727 "line": 89
4142 }, 4728 },
4143 "file": "src/lib/Menu.js", 4729 "file": "src/lib/Menu.js",
4144 "id": "menu.view.enterFullScreen", 4730 "id": "menu.view.enterFullScreen",
4145 "start": { 4731 "start": {
4146 "column": 19, 4732 "column": 19,
4147 "line": 83 4733 "line": 86
4148 } 4734 }
4149 }, 4735 },
4150 { 4736 {
4151 "defaultMessage": "!!!Exit Full Screen", 4737 "defaultMessage": "!!!Exit Full Screen",
4152 "end": { 4738 "end": {
4153 "column": 3, 4739 "column": 3,
4154 "line": 90 4740 "line": 93
4155 }, 4741 },
4156 "file": "src/lib/Menu.js", 4742 "file": "src/lib/Menu.js",
4157 "id": "menu.view.exitFullScreen", 4743 "id": "menu.view.exitFullScreen",
4158 "start": { 4744 "start": {
4159 "column": 18, 4745 "column": 18,
4160 "line": 87 4746 "line": 90
4161 } 4747 }
4162 }, 4748 },
4163 { 4749 {
4164 "defaultMessage": "!!!Toggle Full Screen", 4750 "defaultMessage": "!!!Toggle Full Screen",
4165 "end": { 4751 "end": {
4166 "column": 3, 4752 "column": 3,
4167 "line": 94 4753 "line": 97
4168 }, 4754 },
4169 "file": "src/lib/Menu.js", 4755 "file": "src/lib/Menu.js",
4170 "id": "menu.view.toggleFullScreen", 4756 "id": "menu.view.toggleFullScreen",
4171 "start": { 4757 "start": {
4172 "column": 20, 4758 "column": 20,
4173 "line": 91 4759 "line": 94
4174 } 4760 }
4175 }, 4761 },
4176 { 4762 {
4177 "defaultMessage": "!!!Toggle Developer Tools", 4763 "defaultMessage": "!!!Toggle Developer Tools",
4178 "end": { 4764 "end": {
4179 "column": 3, 4765 "column": 3,
4180 "line": 98 4766 "line": 101
4181 }, 4767 },
4182 "file": "src/lib/Menu.js", 4768 "file": "src/lib/Menu.js",
4183 "id": "menu.view.toggleDevTools", 4769 "id": "menu.view.toggleDevTools",
4184 "start": { 4770 "start": {
4185 "column": 18, 4771 "column": 18,
4186 "line": 95 4772 "line": 98
4187 } 4773 }
4188 }, 4774 },
4189 { 4775 {
4190 "defaultMessage": "!!!Toggle Service Developer Tools", 4776 "defaultMessage": "!!!Toggle Todos Developer Tools",
4191 "end": { 4777 "end": {
4192 "column": 3, 4778 "column": 3,
4779 "line": 105
4780 },
4781 "file": "src/lib/Menu.js",
4782 "id": "menu.view.toggleTodosDevTools",
4783 "start": {
4784 "column": 23,
4193 "line": 102 4785 "line": 102
4786 }
4787 },
4788 {
4789 "defaultMessage": "!!!Toggle Service Developer Tools",
4790 "end": {
4791 "column": 3,
4792 "line": 109
4194 }, 4793 },
4195 "file": "src/lib/Menu.js", 4794 "file": "src/lib/Menu.js",
4196 "id": "menu.view.toggleServiceDevTools", 4795 "id": "menu.view.toggleServiceDevTools",
4197 "start": { 4796 "start": {
4198 "column": 25, 4797 "column": 25,
4199 "line": 99 4798 "line": 106
4200 } 4799 }
4201 }, 4800 },
4202 { 4801 {
4203 "defaultMessage": "!!!Reload Service", 4802 "defaultMessage": "!!!Reload Service",
4204 "end": { 4803 "end": {
4205 "column": 3, 4804 "column": 3,
4206 "line": 106 4805 "line": 113
4207 }, 4806 },
4208 "file": "src/lib/Menu.js", 4807 "file": "src/lib/Menu.js",
4209 "id": "menu.view.reloadService", 4808 "id": "menu.view.reloadService",
4210 "start": { 4809 "start": {
4211 "column": 17, 4810 "column": 17,
4212 "line": 103 4811 "line": 110
4213 } 4812 }
4214 }, 4813 },
4215 { 4814 {
4216 "defaultMessage": "!!!Reload Franz", 4815 "defaultMessage": "!!!Reload Franz",
4217 "end": { 4816 "end": {
4218 "column": 3, 4817 "column": 3,
4219 "line": 110 4818 "line": 117
4220 }, 4819 },
4221 "file": "src/lib/Menu.js", 4820 "file": "src/lib/Menu.js",
4222 "id": "menu.view.reloadFranz", 4821 "id": "menu.view.reloadFranz",
4223 "start": { 4822 "start": {
4224 "column": 15, 4823 "column": 15,
4225 "line": 107 4824 "line": 114
4226 } 4825 }
4227 }, 4826 },
4228 { 4827 {
4229 "defaultMessage": "!!!Minimize", 4828 "defaultMessage": "!!!Minimize",
4230 "end": { 4829 "end": {
4231 "column": 3, 4830 "column": 3,
4232 "line": 114 4831 "line": 121
4233 }, 4832 },
4234 "file": "src/lib/Menu.js", 4833 "file": "src/lib/Menu.js",
4235 "id": "menu.window.minimize", 4834 "id": "menu.window.minimize",
4236 "start": { 4835 "start": {
4237 "column": 12, 4836 "column": 12,
4238 "line": 111 4837 "line": 118
4239 } 4838 }
4240 }, 4839 },
4241 { 4840 {
4242 "defaultMessage": "!!!Close", 4841 "defaultMessage": "!!!Close",
4243 "end": { 4842 "end": {
4244 "column": 3, 4843 "column": 3,
4245 "line": 118 4844 "line": 125
4246 }, 4845 },
4247 "file": "src/lib/Menu.js", 4846 "file": "src/lib/Menu.js",
4248 "id": "menu.window.close", 4847 "id": "menu.window.close",
4249 "start": { 4848 "start": {
4250 "column": 9, 4849 "column": 9,
4251 "line": 115 4850 "line": 122
4252 } 4851 }
4253 }, 4852 },
4254 { 4853 {
4255 "defaultMessage": "!!!Learn More", 4854 "defaultMessage": "!!!Learn More",
4256 "end": { 4855 "end": {
4257 "column": 3, 4856 "column": 3,
4258 "line": 122 4857 "line": 129
4259 }, 4858 },
4260 "file": "src/lib/Menu.js", 4859 "file": "src/lib/Menu.js",
4261 "id": "menu.help.learnMore", 4860 "id": "menu.help.learnMore",
4262 "start": { 4861 "start": {
4263 "column": 13, 4862 "column": 13,
4264 "line": 119 4863 "line": 126
4265 } 4864 }
4266 }, 4865 },
4267 { 4866 {
4268 "defaultMessage": "!!!Changelog", 4867 "defaultMessage": "!!!Changelog",
4269 "end": { 4868 "end": {
4270 "column": 3, 4869 "column": 3,
4271 "line": 126 4870 "line": 133
4272 }, 4871 },
4273 "file": "src/lib/Menu.js", 4872 "file": "src/lib/Menu.js",
4274 "id": "menu.help.changelog", 4873 "id": "menu.help.changelog",
4275 "start": { 4874 "start": {
4276 "column": 13, 4875 "column": 13,
4277 "line": 123 4876 "line": 130
4278 } 4877 }
4279 }, 4878 },
4280 { 4879 {
4281 "defaultMessage": "!!!Support", 4880 "defaultMessage": "!!!Support",
4282 "end": { 4881 "end": {
4283 "column": 3, 4882 "column": 3,
4284 "line": 130 4883 "line": 137
4285 }, 4884 },
4286 "file": "src/lib/Menu.js", 4885 "file": "src/lib/Menu.js",
4287 "id": "menu.help.support", 4886 "id": "menu.help.support",
4288 "start": { 4887 "start": {
4289 "column": 11, 4888 "column": 11,
4290 "line": 127 4889 "line": 134
4890 }
4891 },
4892 {
4893 "defaultMessage": "!!!Copy Debug Information",
4894 "end": {
4895 "column": 3,
4896 "line": 141
4897 },
4898 "file": "src/lib/Menu.js",
4899 "id": "menu.help.debugInfo",
4900 "start": {
4901 "column": 13,
4902 "line": 138
4903 }
4904 },
4905 {
4906 "defaultMessage": "!!!Franz Debug Information",
4907 "end": {
4908 "column": 3,
4909 "line": 145
4910 },
4911 "file": "src/lib/Menu.js",
4912 "id": "menu.help.debugInfoCopiedHeadline",
4913 "start": {
4914 "column": 27,
4915 "line": 142
4916 }
4917 },
4918 {
4919 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.",
4920 "end": {
4921 "column": 3,
4922 "line": 149
4923 },
4924 "file": "src/lib/Menu.js",
4925 "id": "menu.help.debugInfoCopiedBody",
4926 "start": {
4927 "column": 23,
4928 "line": 146
4291 } 4929 }
4292 }, 4930 },
4293 { 4931 {
4294 "defaultMessage": "!!!Terms of Service", 4932 "defaultMessage": "!!!Terms of Service",
4295 "end": { 4933 "end": {
4296 "column": 3, 4934 "column": 3,
4297 "line": 134 4935 "line": 153
4298 }, 4936 },
4299 "file": "src/lib/Menu.js", 4937 "file": "src/lib/Menu.js",
4300 "id": "menu.help.tos", 4938 "id": "menu.help.tos",
4301 "start": { 4939 "start": {
4302 "column": 7, 4940 "column": 7,
4303 "line": 131 4941 "line": 150
4304 } 4942 }
4305 }, 4943 },
4306 { 4944 {
4307 "defaultMessage": "!!!Privacy Statement", 4945 "defaultMessage": "!!!Privacy Statement",
4308 "end": { 4946 "end": {
4309 "column": 3, 4947 "column": 3,
4310 "line": 138 4948 "line": 157
4311 }, 4949 },
4312 "file": "src/lib/Menu.js", 4950 "file": "src/lib/Menu.js",
4313 "id": "menu.help.privacy", 4951 "id": "menu.help.privacy",
4314 "start": { 4952 "start": {
4315 "column": 11, 4953 "column": 11,
4316 "line": 135 4954 "line": 154
4317 } 4955 }
4318 }, 4956 },
4319 { 4957 {
4320 "defaultMessage": "!!!File", 4958 "defaultMessage": "!!!File",
4321 "end": { 4959 "end": {
4322 "column": 3, 4960 "column": 3,
4323 "line": 142 4961 "line": 161
4324 }, 4962 },
4325 "file": "src/lib/Menu.js", 4963 "file": "src/lib/Menu.js",
4326 "id": "menu.file", 4964 "id": "menu.file",
4327 "start": { 4965 "start": {
4328 "column": 8, 4966 "column": 8,
4329 "line": 139 4967 "line": 158
4330 } 4968 }
4331 }, 4969 },
4332 { 4970 {
4333 "defaultMessage": "!!!View", 4971 "defaultMessage": "!!!View",
4334 "end": { 4972 "end": {
4335 "column": 3, 4973 "column": 3,
4336 "line": 146 4974 "line": 165
4337 }, 4975 },
4338 "file": "src/lib/Menu.js", 4976 "file": "src/lib/Menu.js",
4339 "id": "menu.view", 4977 "id": "menu.view",
4340 "start": { 4978 "start": {
4341 "column": 8, 4979 "column": 8,
4342 "line": 143 4980 "line": 162
4343 } 4981 }
4344 }, 4982 },
4345 { 4983 {
4346 "defaultMessage": "!!!Services", 4984 "defaultMessage": "!!!Services",
4347 "end": { 4985 "end": {
4348 "column": 3, 4986 "column": 3,
4349 "line": 150 4987 "line": 169
4350 }, 4988 },
4351 "file": "src/lib/Menu.js", 4989 "file": "src/lib/Menu.js",
4352 "id": "menu.services", 4990 "id": "menu.services",
4353 "start": { 4991 "start": {
4354 "column": 12, 4992 "column": 12,
4355 "line": 147 4993 "line": 166
4356 } 4994 }
4357 }, 4995 },
4358 { 4996 {
4359 "defaultMessage": "!!!Window", 4997 "defaultMessage": "!!!Window",
4360 "end": { 4998 "end": {
4361 "column": 3, 4999 "column": 3,
4362 "line": 154 5000 "line": 173
4363 }, 5001 },
4364 "file": "src/lib/Menu.js", 5002 "file": "src/lib/Menu.js",
4365 "id": "menu.window", 5003 "id": "menu.window",
4366 "start": { 5004 "start": {
4367 "column": 10, 5005 "column": 10,
4368 "line": 151 5006 "line": 170
4369 } 5007 }
4370 }, 5008 },
4371 { 5009 {
4372 "defaultMessage": "!!!Help", 5010 "defaultMessage": "!!!Help",
4373 "end": { 5011 "end": {
4374 "column": 3, 5012 "column": 3,
4375 "line": 158 5013 "line": 177
4376 }, 5014 },
4377 "file": "src/lib/Menu.js", 5015 "file": "src/lib/Menu.js",
4378 "id": "menu.help", 5016 "id": "menu.help",
4379 "start": { 5017 "start": {
4380 "column": 8, 5018 "column": 8,
4381 "line": 155 5019 "line": 174
4382 } 5020 }
4383 }, 5021 },
4384 { 5022 {
4385 "defaultMessage": "!!!About Franz", 5023 "defaultMessage": "!!!About Franz",
4386 "end": { 5024 "end": {
4387 "column": 3, 5025 "column": 3,
4388 "line": 162 5026 "line": 181
4389 }, 5027 },
4390 "file": "src/lib/Menu.js", 5028 "file": "src/lib/Menu.js",
4391 "id": "menu.app.about", 5029 "id": "menu.app.about",
4392 "start": { 5030 "start": {
4393 "column": 9, 5031 "column": 9,
4394 "line": 159 5032 "line": 178
4395 } 5033 }
4396 }, 5034 },
4397 { 5035 {
4398 "defaultMessage": "!!!What's new?", 5036 "defaultMessage": "!!!What's new?",
4399 "end": { 5037 "end": {
4400 "column": 3, 5038 "column": 3,
4401 "line": 166 5039 "line": 185
4402 }, 5040 },
4403 "file": "src/lib/Menu.js", 5041 "file": "src/lib/Menu.js",
4404 "id": "menu.app.announcement", 5042 "id": "menu.app.announcement",
4405 "start": { 5043 "start": {
4406 "column": 16, 5044 "column": 16,
4407 "line": 163 5045 "line": 182
4408 } 5046 }
4409 }, 5047 },
4410 { 5048 {
4411 "defaultMessage": "!!!Settings", 5049 "defaultMessage": "!!!Settings",
4412 "end": { 5050 "end": {
4413 "column": 3, 5051 "column": 3,
4414 "line": 170 5052 "line": 189
4415 }, 5053 },
4416 "file": "src/lib/Menu.js", 5054 "file": "src/lib/Menu.js",
4417 "id": "menu.app.settings", 5055 "id": "menu.app.settings",
4418 "start": { 5056 "start": {
4419 "column": 12, 5057 "column": 12,
4420 "line": 167 5058 "line": 186
4421 } 5059 }
4422 }, 5060 },
4423 { 5061 {
4424 "defaultMessage": "!!!Check for updates", 5062 "defaultMessage": "!!!Check for updates",
4425 "end": { 5063 "end": {
4426 "column": 3, 5064 "column": 3,
4427 "line": 174 5065 "line": 193
4428 }, 5066 },
4429 "file": "src/lib/Menu.js", 5067 "file": "src/lib/Menu.js",
4430 "id": "menu.app.checkForUpdates", 5068 "id": "menu.app.checkForUpdates",
4431 "start": { 5069 "start": {
4432 "column": 19, 5070 "column": 19,
4433 "line": 171 5071 "line": 190
4434 } 5072 }
4435 }, 5073 },
4436 { 5074 {
4437 "defaultMessage": "!!!Hide", 5075 "defaultMessage": "!!!Hide",
4438 "end": { 5076 "end": {
4439 "column": 3, 5077 "column": 3,
4440 "line": 178 5078 "line": 197
4441 }, 5079 },
4442 "file": "src/lib/Menu.js", 5080 "file": "src/lib/Menu.js",
4443 "id": "menu.app.hide", 5081 "id": "menu.app.hide",
4444 "start": { 5082 "start": {
4445 "column": 8, 5083 "column": 8,
4446 "line": 175 5084 "line": 194
4447 } 5085 }
4448 }, 5086 },
4449 { 5087 {
4450 "defaultMessage": "!!!Hide Others", 5088 "defaultMessage": "!!!Hide Others",
4451 "end": { 5089 "end": {
4452 "column": 3, 5090 "column": 3,
4453 "line": 182 5091 "line": 201
4454 }, 5092 },
4455 "file": "src/lib/Menu.js", 5093 "file": "src/lib/Menu.js",
4456 "id": "menu.app.hideOthers", 5094 "id": "menu.app.hideOthers",
4457 "start": { 5095 "start": {
4458 "column": 14, 5096 "column": 14,
4459 "line": 179 5097 "line": 198
4460 } 5098 }
4461 }, 5099 },
4462 { 5100 {
4463 "defaultMessage": "!!!Unhide", 5101 "defaultMessage": "!!!Unhide",
4464 "end": { 5102 "end": {
4465 "column": 3, 5103 "column": 3,
4466 "line": 186 5104 "line": 205
4467 }, 5105 },
4468 "file": "src/lib/Menu.js", 5106 "file": "src/lib/Menu.js",
4469 "id": "menu.app.unhide", 5107 "id": "menu.app.unhide",
4470 "start": { 5108 "start": {
4471 "column": 10, 5109 "column": 10,
4472 "line": 183 5110 "line": 202
4473 } 5111 }
4474 }, 5112 },
4475 { 5113 {
4476 "defaultMessage": "!!!Quit", 5114 "defaultMessage": "!!!Quit",
4477 "end": { 5115 "end": {
4478 "column": 3, 5116 "column": 3,
4479 "line": 190 5117 "line": 209
4480 }, 5118 },
4481 "file": "src/lib/Menu.js", 5119 "file": "src/lib/Menu.js",
4482 "id": "menu.app.quit", 5120 "id": "menu.app.quit",
4483 "start": { 5121 "start": {
4484 "column": 8, 5122 "column": 8,
4485 "line": 187 5123 "line": 206
4486 } 5124 }
4487 }, 5125 },
4488 { 5126 {
4489 "defaultMessage": "!!!Add New Service...", 5127 "defaultMessage": "!!!Add New Service...",
4490 "end": { 5128 "end": {
4491 "column": 3, 5129 "column": 3,
4492 "line": 194 5130 "line": 213
4493 }, 5131 },
4494 "file": "src/lib/Menu.js", 5132 "file": "src/lib/Menu.js",
4495 "id": "menu.services.addNewService", 5133 "id": "menu.services.addNewService",
4496 "start": { 5134 "start": {
4497 "column": 17, 5135 "column": 17,
4498 "line": 191 5136 "line": 210
4499 } 5137 }
4500 }, 5138 },
4501 { 5139 {
4502 "defaultMessage": "!!!Add New Workspace...", 5140 "defaultMessage": "!!!Add New Workspace...",
4503 "end": { 5141 "end": {
4504 "column": 3, 5142 "column": 3,
4505 "line": 198 5143 "line": 217
4506 }, 5144 },
4507 "file": "src/lib/Menu.js", 5145 "file": "src/lib/Menu.js",
4508 "id": "menu.workspaces.addNewWorkspace", 5146 "id": "menu.workspaces.addNewWorkspace",
4509 "start": { 5147 "start": {
4510 "column": 19, 5148 "column": 19,
4511 "line": 195 5149 "line": 214
4512 } 5150 }
4513 }, 5151 },
4514 { 5152 {
4515 "defaultMessage": "!!!Open workspace drawer", 5153 "defaultMessage": "!!!Open workspace drawer",
4516 "end": { 5154 "end": {
4517 "column": 3, 5155 "column": 3,
4518 "line": 202 5156 "line": 221
4519 }, 5157 },
4520 "file": "src/lib/Menu.js", 5158 "file": "src/lib/Menu.js",
4521 "id": "menu.workspaces.openWorkspaceDrawer", 5159 "id": "menu.workspaces.openWorkspaceDrawer",
4522 "start": { 5160 "start": {
4523 "column": 23, 5161 "column": 23,
4524 "line": 199 5162 "line": 218
4525 } 5163 }
4526 }, 5164 },
4527 { 5165 {
4528 "defaultMessage": "!!!Close workspace drawer", 5166 "defaultMessage": "!!!Close workspace drawer",
4529 "end": { 5167 "end": {
4530 "column": 3, 5168 "column": 3,
4531 "line": 206 5169 "line": 225
4532 }, 5170 },
4533 "file": "src/lib/Menu.js", 5171 "file": "src/lib/Menu.js",
4534 "id": "menu.workspaces.closeWorkspaceDrawer", 5172 "id": "menu.workspaces.closeWorkspaceDrawer",
4535 "start": { 5173 "start": {
4536 "column": 24, 5174 "column": 24,
4537 "line": 203 5175 "line": 222
4538 } 5176 }
4539 }, 5177 },
4540 { 5178 {
4541 "defaultMessage": "!!!Activate next service...", 5179 "defaultMessage": "!!!Activate next service...",
4542 "end": { 5180 "end": {
4543 "column": 3, 5181 "column": 3,
4544 "line": 210 5182 "line": 229
4545 }, 5183 },
4546 "file": "src/lib/Menu.js", 5184 "file": "src/lib/Menu.js",
4547 "id": "menu.services.setNextServiceActive", 5185 "id": "menu.services.setNextServiceActive",
4548 "start": { 5186 "start": {
4549 "column": 23, 5187 "column": 23,
4550 "line": 207 5188 "line": 226
4551 } 5189 }
4552 }, 5190 },
4553 { 5191 {
4554 "defaultMessage": "!!!Activate previous service...", 5192 "defaultMessage": "!!!Activate previous service...",
4555 "end": { 5193 "end": {
4556 "column": 3, 5194 "column": 3,
4557 "line": 214 5195 "line": 233
4558 }, 5196 },
4559 "file": "src/lib/Menu.js", 5197 "file": "src/lib/Menu.js",
4560 "id": "menu.services.activatePreviousService", 5198 "id": "menu.services.activatePreviousService",
4561 "start": { 5199 "start": {
4562 "column": 27, 5200 "column": 27,
4563 "line": 211 5201 "line": 230
4564 } 5202 }
4565 }, 5203 },
4566 { 5204 {
4567 "defaultMessage": "!!!Disable notifications & audio", 5205 "defaultMessage": "!!!Disable notifications & audio",
4568 "end": { 5206 "end": {
4569 "column": 3, 5207 "column": 3,
4570 "line": 218 5208 "line": 237
4571 }, 5209 },
4572 "file": "src/lib/Menu.js", 5210 "file": "src/lib/Menu.js",
4573 "id": "sidebar.muteApp", 5211 "id": "sidebar.muteApp",
4574 "start": { 5212 "start": {
4575 "column": 11, 5213 "column": 11,
4576 "line": 215 5214 "line": 234
4577 } 5215 }
4578 }, 5216 },
4579 { 5217 {
4580 "defaultMessage": "!!!Enable notifications & audio", 5218 "defaultMessage": "!!!Enable notifications & audio",
4581 "end": { 5219 "end": {
4582 "column": 3, 5220 "column": 3,
4583 "line": 222 5221 "line": 241
4584 }, 5222 },
4585 "file": "src/lib/Menu.js", 5223 "file": "src/lib/Menu.js",
4586 "id": "sidebar.unmuteApp", 5224 "id": "sidebar.unmuteApp",
4587 "start": { 5225 "start": {
4588 "column": 13, 5226 "column": 13,
4589 "line": 219 5227 "line": 238
4590 } 5228 }
4591 }, 5229 },
4592 { 5230 {
4593 "defaultMessage": "!!!Workspaces", 5231 "defaultMessage": "!!!Workspaces",
4594 "end": { 5232 "end": {
4595 "column": 3, 5233 "column": 3,
4596 "line": 226 5234 "line": 245
4597 }, 5235 },
4598 "file": "src/lib/Menu.js", 5236 "file": "src/lib/Menu.js",
4599 "id": "menu.workspaces", 5237 "id": "menu.workspaces",
4600 "start": { 5238 "start": {
4601 "column": 14, 5239 "column": 14,
4602 "line": 223 5240 "line": 242
4603 } 5241 }
4604 }, 5242 },
4605 { 5243 {
4606 "defaultMessage": "!!!Default", 5244 "defaultMessage": "!!!Default",
4607 "end": { 5245 "end": {
4608 "column": 3, 5246 "column": 3,
4609 "line": 230 5247 "line": 249
4610 }, 5248 },
4611 "file": "src/lib/Menu.js", 5249 "file": "src/lib/Menu.js",
4612 "id": "menu.workspaces.defaultWorkspace", 5250 "id": "menu.workspaces.defaultWorkspace",
4613 "start": { 5251 "start": {
4614 "column": 20, 5252 "column": 20,
4615 "line": 227 5253 "line": 246
5254 }
5255 },
5256 {
5257 "defaultMessage": "!!!Todos",
5258 "end": {
5259 "column": 3,
5260 "line": 253
5261 },
5262 "file": "src/lib/Menu.js",
5263 "id": "menu.todos",
5264 "start": {
5265 "column": 9,
5266 "line": 250
5267 }
5268 },
5269 {
5270 "defaultMessage": "!!!Open Todos drawer",
5271 "end": {
5272 "column": 3,
5273 "line": 257
5274 },
5275 "file": "src/lib/Menu.js",
5276 "id": "menu.Todoss.openTodosDrawer",
5277 "start": {
5278 "column": 19,
5279 "line": 254
5280 }
5281 },
5282 {
5283 "defaultMessage": "!!!Close Todos drawer",
5284 "end": {
5285 "column": 3,
5286 "line": 261
5287 },
5288 "file": "src/lib/Menu.js",
5289 "id": "menu.Todoss.closeTodosDrawer",
5290 "start": {
5291 "column": 20,
5292 "line": 258
4616 } 5293 }
4617 } 5294 }
4618 ], 5295 ],
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index e2edbd596..727eb2884 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -2,9 +2,12 @@
2 "app.errorHandler.action": "Reload", 2 "app.errorHandler.action": "Reload",
3 "app.errorHandler.headline": "Something went wrong", 3 "app.errorHandler.headline": "Something went wrong",
4 "feature.announcements.changelog.headline": "Changes in Franz {version}", 4 "feature.announcements.changelog.headline": "Changes in Franz {version}",
5 "feature.delayApp.action": "Get a Franz Supporter License",
6 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", 5 "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting",
7 "feature.delayApp.text": "Franz will continue in {seconds} seconds.", 6 "feature.delayApp.text": "Franz will continue in {seconds} seconds.",
7 "feature.delayApp.trial.action": "Yes, I want the free 14 day trial of Franz Professional",
8 "feature.delayApp.trial.headline": "Get the free Franz Professional 14 day trial and skip the line",
9 "feature.delayApp.upgrade.action": "Get a Franz Supporter License",
10 "feature.serviceLimit.limitReached": "You have added {amount} out of {limit} services that are included in your plan. Please upgrade your account to add more services.",
8 "feature.shareFranz.action.email": "Send as email", 11 "feature.shareFranz.action.email": "Send as email",
9 "feature.shareFranz.action.facebook": "Share on Facebook", 12 "feature.shareFranz.action.facebook": "Share on Facebook",
10 "feature.shareFranz.action.twitter": "Share on Twitter", 13 "feature.shareFranz.action.twitter": "Share on Twitter",
@@ -27,6 +30,7 @@
27 "infobar.buttonReloadServices": "Reload services", 30 "infobar.buttonReloadServices": "Reload services",
28 "infobar.requiredRequestsFailed": "Could not load services and user information", 31 "infobar.requiredRequestsFailed": "Could not load services and user information",
29 "infobar.servicesUpdated": "Your services have been updated.", 32 "infobar.servicesUpdated": "Your services have been updated.",
33 "infobar.trialActivated": "Your trial was successfully activated. Happy messaging!",
30 "infobar.updateAvailable": "A new update for Franz is available.", 34 "infobar.updateAvailable": "A new update for Franz is available.",
31 "invite.email.label": "Email address", 35 "invite.email.label": "Email address",
32 "invite.headline.friends": "Invite 3 of your friends or colleagues", 36 "invite.headline.friends": "Invite 3 of your friends or colleagues",
@@ -43,6 +47,8 @@
43 "login.serverLogout": "Your session expired, please login again.", 47 "login.serverLogout": "Your session expired, please login again.",
44 "login.submit.label": "Sign in", 48 "login.submit.label": "Sign in",
45 "login.tokenExpired": "Your session expired, please login again.", 49 "login.tokenExpired": "Your session expired, please login again.",
50 "menu.Todoss.closeTodosDrawer": "Close Todos drawer",
51 "menu.Todoss.openTodosDrawer": "Open Todos drawer",
46 "menu.app.about": "About Franz", 52 "menu.app.about": "About Franz",
47 "menu.app.announcement": "What's new?", 53 "menu.app.announcement": "What's new?",
48 "menu.app.checkForUpdates": "Check for updates", 54 "menu.app.checkForUpdates": "Check for updates",
@@ -68,6 +74,9 @@
68 "menu.file": "File", 74 "menu.file": "File",
69 "menu.help": "Help", 75 "menu.help": "Help",
70 "menu.help.changelog": "Changelog", 76 "menu.help.changelog": "Changelog",
77 "menu.help.debugInfo": "Copy Debug Information",
78 "menu.help.debugInfoCopiedBody": "Your Debug Information has been copied to your clipboard.",
79 "menu.help.debugInfoCopiedHeadline": "Franz Debug Information",
71 "menu.help.learnMore": "Learn More", 80 "menu.help.learnMore": "Learn More",
72 "menu.help.privacy": "Privacy Statement", 81 "menu.help.privacy": "Privacy Statement",
73 "menu.help.support": "Support", 82 "menu.help.support": "Support",
@@ -76,6 +85,7 @@
76 "menu.services.activatePreviousService": "Activate previous service", 85 "menu.services.activatePreviousService": "Activate previous service",
77 "menu.services.addNewService": "Add New Service...", 86 "menu.services.addNewService": "Add New Service...",
78 "menu.services.setNextServiceActive": "Activate next service", 87 "menu.services.setNextServiceActive": "Activate next service",
88 "menu.todos": "Todos",
79 "menu.view": "View", 89 "menu.view": "View",
80 "menu.view.enterFullScreen": "Enter Full Screen", 90 "menu.view.enterFullScreen": "Enter Full Screen",
81 "menu.view.exitFullScreen": "Exit Full Screen", 91 "menu.view.exitFullScreen": "Exit Full Screen",
@@ -85,6 +95,7 @@
85 "menu.view.toggleDevTools": "Toggle Developer Tools", 95 "menu.view.toggleDevTools": "Toggle Developer Tools",
86 "menu.view.toggleFullScreen": "Toggle Full Screen", 96 "menu.view.toggleFullScreen": "Toggle Full Screen",
87 "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools", 97 "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools",
98 "menu.view.toggleTodosDevTools": "Toggle Todos Developer Tools",
88 "menu.view.zoomIn": "Zoom In", 99 "menu.view.zoomIn": "Zoom In",
89 "menu.view.zoomOut": "Zoom Out", 100 "menu.view.zoomOut": "Zoom Out",
90 "menu.window": "Window", 101 "menu.window": "Window",
@@ -103,10 +114,33 @@
103 "password.submit.label": "Submit", 114 "password.submit.label": "Submit",
104 "password.successInfo": "Please check your email", 115 "password.successInfo": "Please check your email",
105 "premiumFeature.button.upgradeAccount": "Upgrade account", 116 "premiumFeature.button.upgradeAccount": "Upgrade account",
106 "pricing.headline": "Support Franz", 117 "pricing.features.adFree": "Forever ad-free",
107 "pricing.link.skipPayment": "I don't want to support the development of Franz.", 118 "pricing.features.appDelays": "No Waiting Screens",
108 "pricing.submit.label": "I want to support the development of Franz", 119 "pricing.features.customWebsites": "Add Custom Websites",
109 "pricing.support.label": "Select your support plan", 120 "pricing.features.onPremise": "On-premise & other Hosted Services",
121 "pricing.features.serviceProxies": "Service Proxies",
122 "pricing.features.spellchecker": "Spellchecker support",
123 "pricing.features.teamManagement": "Team Management",
124 "pricing.features.thirdPartyServices": "Install 3rd party services",
125 "pricing.features.unlimitedServices": "Add unlimited services",
126 "pricing.features.workspaces": "Workspaces",
127 "pricing.plan.free": "Franz Free",
128 "pricing.plan.legacy": "Franz Premium",
129 "pricing.plan.personal": "Franz Personal",
130 "pricing.plan.personal-monthly": "Franz Personal Monthly",
131 "pricing.plan.personal-yearly": "Franz Personal Yearly",
132 "pricing.plan.pro": "Franz Professional",
133 "pricing.plan.pro-monthly": "Franz Professional Monthly",
134 "pricing.plan.pro-yearly": "Franz Professional Yearly",
135 "pricing.trial.cta.accept": "Yes, upgrade my account to Franz Professional",
136 "pricing.trial.cta.skip": "Continue to Franz",
137 "pricing.trial.error": "Sorry, we could not activate your trial!",
138 "pricing.trial.features.headline": "Franz Professional includes:",
139 "pricing.trial.headline": "Franz Professional",
140 "pricing.trial.subheadline": "Your personal welcome offer:",
141 "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days",
142 "pricing.trial.terms.headline": "No strings attached",
143 "pricing.trial.terms.noCreditCard": "No credit card required",
110 "service.crashHandler.action": "Reload {name}", 144 "service.crashHandler.action": "Reload {name}",
111 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", 145 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds",
112 "service.crashHandler.headline": "Oh no!", 146 "service.crashHandler.headline": "Oh no!",
@@ -118,6 +152,11 @@
118 "service.errorHandler.headline": "Oh no!", 152 "service.errorHandler.headline": "Oh no!",
119 "service.errorHandler.message": "Error", 153 "service.errorHandler.message": "Error",
120 "service.errorHandler.text": "{name} has failed to load.", 154 "service.errorHandler.text": "{name} has failed to load.",
155 "service.restrictedHandler.action": "Upgrade Account",
156 "service.restrictedHandler.customUrl.headline": "Franz Professional Plan required",
157 "service.restrictedHandler.customUrl.text": "Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
158 "service.restrictedHandler.serviceLimit.headline": "You have reached your service limit.",
159 "service.restrictedHandler.serviceLimit.text": "Please upgrade your account to use more than {count} services.",
121 "service.webviewLoader.loading": "Loading", 160 "service.webviewLoader.loading": "Loading",
122 "services.getStarted": "Get started", 161 "services.getStarted": "Get started",
123 "services.welcome": "Welcome to Franz", 162 "services.welcome": "Welcome to Franz",
@@ -135,13 +174,19 @@
135 "settings.account.headlinePassword": "Change password", 174 "settings.account.headlinePassword": "Change password",
136 "settings.account.headlineProfile": "Update profile", 175 "settings.account.headlineProfile": "Update profile",
137 "settings.account.headlineSubscription": "Your subscription", 176 "settings.account.headlineSubscription": "Your subscription",
138 "settings.account.headlineUpgrade": "Upgrade your account & support Franz", 177 "settings.account.headlineTrialUpgrade": "Get the free 14 day Franz Professional Trial",
178 "settings.account.headlineUpgradeAccount": "Upgrade your account & get the full Franz experience",
139 "settings.account.invoiceDownload": "Download", 179 "settings.account.invoiceDownload": "Download",
140 "settings.account.manageSubscription.label": "Manage your subscription", 180 "settings.account.manageSubscription.label": "Manage your subscription",
141 "settings.account.successInfo": "Your changes have been saved", 181 "settings.account.successInfo": "Your changes have been saved",
182 "settings.account.trial": "Free Trial",
183 "settings.account.trialEndsIn": "Your free trial ends in {duration}.",
184 "settings.account.trialUpdateBillingInfo": "Please update your billing info to continue using {license} after your trial period.",
142 "settings.account.tryReloadServices": "Try again", 185 "settings.account.tryReloadServices": "Try again",
143 "settings.account.tryReloadUserInfoRequest": "Try again", 186 "settings.account.tryReloadUserInfoRequest": "Try again",
187 "settings.account.upgradeToPro.label": "Upgrade to Franz Professional",
144 "settings.account.userInfoRequestFailed": "Could not load user information", 188 "settings.account.userInfoRequestFailed": "Could not load user information",
189 "settings.account.yourLicense": "Your Franz License",
145 "settings.app.buttonClearAllCache": "Clear cache", 190 "settings.app.buttonClearAllCache": "Clear cache",
146 "settings.app.buttonInstallUpdate": "Restart & install update", 191 "settings.app.buttonInstallUpdate": "Restart & install update",
147 "settings.app.buttonSearchForUpdate": "Check for updates", 192 "settings.app.buttonSearchForUpdate": "Check for updates",
@@ -182,7 +227,13 @@
182 "settings.navigation.yourServices": "Your services", 227 "settings.navigation.yourServices": "Your services",
183 "settings.navigation.yourWorkspaces": "Your workspaces", 228 "settings.navigation.yourWorkspaces": "Your workspaces",
184 "settings.recipes.all": "All services", 229 "settings.recipes.all": "All services",
185 "settings.recipes.dev": "Development", 230 "settings.recipes.custom": "Custom Services",
231 "settings.recipes.customService.headline.communityRecipes": "Community Services",
232 "settings.recipes.customService.headline.customRecipes": "Custom Service Recipes",
233 "settings.recipes.customService.headline.devRecipes": "Your Development Service Recipes",
234 "settings.recipes.customService.intro": "To add a custom service, copy the service recipe to:",
235 "settings.recipes.customService.openDevDocs": "Developer Documentation",
236 "settings.recipes.customService.openFolder": "Open folder",
186 "settings.recipes.headline": "Available services", 237 "settings.recipes.headline": "Available services",
187 "settings.recipes.missingService": "Missing a service?", 238 "settings.recipes.missingService": "Missing a service?",
188 "settings.recipes.mostPopular": "Most popular", 239 "settings.recipes.mostPopular": "Most popular",
@@ -274,7 +325,6 @@
274 "sidebar.openWorkspaceDrawer": "Open workspace drawer", 325 "sidebar.openWorkspaceDrawer": "Open workspace drawer",
275 "sidebar.settings": "Settings", 326 "sidebar.settings": "Settings",
276 "sidebar.unmuteApp": "Enable notifications & audio", 327 "sidebar.unmuteApp": "Enable notifications & audio",
277 "signup.company.label": "Company",
278 "signup.email.label": "Email address", 328 "signup.email.label": "Email address",
279 "signup.emailDuplicate": "A user with that email address already exists", 329 "signup.emailDuplicate": "A user with that email address already exists",
280 "signup.firstname.label": "First Name", 330 "signup.firstname.label": "First Name",
@@ -286,20 +336,12 @@
286 "signup.link.login": "Already have an account, sign in?", 336 "signup.link.login": "Already have an account, sign in?",
287 "signup.password.label": "Password", 337 "signup.password.label": "Password",
288 "signup.submit.label": "Create account", 338 "signup.submit.label": "Create account",
289 "subscription.euTaxInfo": "EU residents: local sales tax may apply", 339 "subscription.cta.activateTrial": "Yes, start the free Franz Professional trial",
290 "subscription.features.ads": "No ads, ever!", 340 "subscription.cta.allOptions": "See all options",
291 "subscription.features.comingSoon": "coming soon", 341 "subscription.cta.choosePlan": "Choose your plan",
292 "subscription.features.noInterruptions": "No app delays & nagging to upgrade license", 342 "subscription.includedProFeatures": "The Franz Professional Plan includes:",
293 "subscription.features.onpremise.mattermost": "Add on-premise/hosted services like Mattermost", 343 "subscription.teaser.includedFeatures": "Paid Franz Plans include:",
294 "subscription.features.proxy": "Proxy support for services", 344 "subscription.teaser.intro": "Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
295 "subscription.features.spellchecker": "Support for spellchecker",
296 "subscription.features.workspaces": "Organize your services in workspaces",
297 "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes",
298 "subscription.paymentSessionError": "Could not initialize payment form",
299 "subscription.submit.label": "I want to support the development of Franz",
300 "subscription.type.free": "free",
301 "subscription.type.month": "month",
302 "subscription.type.year": "year",
303 "subscriptionPopup.buttonCancel": "Cancel", 345 "subscriptionPopup.buttonCancel": "Cancel",
304 "subscriptionPopup.buttonDone": "Done", 346 "subscriptionPopup.buttonDone": "Done",
305 "tabs.item.deleteService": "Delete service", 347 "tabs.item.deleteService": "Delete service",
diff --git a/src/i18n/messages/src/components/TrialActivationInfoBar.json b/src/i18n/messages/src/components/TrialActivationInfoBar.json
new file mode 100644
index 000000000..65dd964a6
--- /dev/null
+++ b/src/i18n/messages/src/components/TrialActivationInfoBar.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "infobar.trialActivated",
4 "defaultMessage": "!!!Your trial was successfully activated. Happy messaging!",
5 "file": "src/components/TrialActivationInfoBar.js",
6 "start": {
7 "line": 11,
8 "column": 11
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json
index f711a55b4..f15617ca5 100644
--- a/src/i18n/messages/src/components/auth/Pricing.json
+++ b/src/i18n/messages/src/components/auth/Pricing.json
@@ -1,53 +1,118 @@
1[ 1[
2 { 2 {
3 "id": "pricing.headline", 3 "id": "pricing.trial.headline",
4 "defaultMessage": "!!!Support Franz", 4 "defaultMessage": "!!!Franz Professional",
5 "file": "src/components/auth/Pricing.js", 5 "file": "src/components/auth/Pricing.js",
6 "start": { 6 "start": {
7 "line": 13, 7 "line": 15,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 16, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
15 { 15 {
16 "id": "pricing.support.label", 16 "id": "pricing.trial.subheadline",
17 "defaultMessage": "!!!Select your support plan", 17 "defaultMessage": "!!!Your personal welcome offer:",
18 "file": "src/components/auth/Pricing.js", 18 "file": "src/components/auth/Pricing.js",
19 "start": { 19 "start": {
20 "line": 17, 20 "line": 19,
21 "column": 23 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 20, 24 "line": 22,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
28 { 28 {
29 "id": "pricing.submit.label", 29 "id": "pricing.trial.terms.headline",
30 "defaultMessage": "!!!Support the development of Franz", 30 "defaultMessage": "!!!No strings attached",
31 "file": "src/components/auth/Pricing.js", 31 "file": "src/components/auth/Pricing.js",
32 "start": { 32 "start": {
33 "line": 21, 33 "line": 23,
34 "column": 29
35 },
36 "end": {
37 "line": 26,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.trial.terms.noCreditCard",
43 "defaultMessage": "!!!No credit card required",
44 "file": "src/components/auth/Pricing.js",
45 "start": {
46 "line": 27,
47 "column": 16
48 },
49 "end": {
50 "line": 30,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.trial.terms.automaticTrialEnd",
56 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
57 "file": "src/components/auth/Pricing.js",
58 "start": {
59 "line": 31,
34 "column": 21 60 "column": 21
35 }, 61 },
36 "end": { 62 "end": {
37 "line": 24, 63 "line": 34,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.trial.error",
69 "defaultMessage": "!!!Sorry, we could not activate your trial!",
70 "file": "src/components/auth/Pricing.js",
71 "start": {
72 "line": 35,
73 "column": 19
74 },
75 "end": {
76 "line": 38,
77 "column": 3
78 }
79 },
80 {
81 "id": "pricing.trial.cta.accept",
82 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional",
83 "file": "src/components/auth/Pricing.js",
84 "start": {
85 "line": 39,
86 "column": 13
87 },
88 "end": {
89 "line": 42,
90 "column": 3
91 }
92 },
93 {
94 "id": "pricing.trial.cta.skip",
95 "defaultMessage": "!!!Continue to Franz",
96 "file": "src/components/auth/Pricing.js",
97 "start": {
98 "line": 43,
99 "column": 11
100 },
101 "end": {
102 "line": 46,
38 "column": 3 103 "column": 3
39 } 104 }
40 }, 105 },
41 { 106 {
42 "id": "pricing.link.skipPayment", 107 "id": "pricing.trial.features.headline",
43 "defaultMessage": "!!!I don't want to support the development of Franz.", 108 "defaultMessage": "!!!Franz Professional includes:",
44 "file": "src/components/auth/Pricing.js", 109 "file": "src/components/auth/Pricing.js",
45 "start": { 110 "start": {
46 "line": 25, 111 "line": 47,
47 "column": 15 112 "column": 20
48 }, 113 },
49 "end": { 114 "end": {
50 "line": 28, 115 "line": 50,
51 "column": 3 116 "column": 3
52 } 117 }
53 } 118 }
diff --git a/src/i18n/messages/src/components/auth/Signup.json b/src/i18n/messages/src/components/auth/Signup.json
index a09745048..2ea71e5ff 100644
--- a/src/i18n/messages/src/components/auth/Signup.json
+++ b/src/i18n/messages/src/components/auth/Signup.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Sign up", 4 "defaultMessage": "!!!Sign up",
5 "file": "src/components/auth/Signup.js", 5 "file": "src/components/auth/Signup.js",
6 "start": { 6 "start": {
7 "line": 18, 7 "line": 17,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 21, 11 "line": 20,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Firstname", 17 "defaultMessage": "!!!Firstname",
18 "file": "src/components/auth/Signup.js", 18 "file": "src/components/auth/Signup.js",
19 "start": { 19 "start": {
20 "line": 22, 20 "line": 21,
21 "column": 18 21 "column": 18
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 25, 24 "line": 24,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Lastname", 30 "defaultMessage": "!!!Lastname",
31 "file": "src/components/auth/Signup.js", 31 "file": "src/components/auth/Signup.js",
32 "start": { 32 "start": {
33 "line": 26, 33 "line": 25,
34 "column": 17 34 "column": 17
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 29, 37 "line": 28,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,24 +43,11 @@
43 "defaultMessage": "!!!Email address", 43 "defaultMessage": "!!!Email address",
44 "file": "src/components/auth/Signup.js", 44 "file": "src/components/auth/Signup.js",
45 "start": { 45 "start": {
46 "line": 30, 46 "line": 29,
47 "column": 14 47 "column": 14
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 33, 50 "line": 32,
51 "column": 3
52 }
53 },
54 {
55 "id": "signup.company.label",
56 "defaultMessage": "!!!Company",
57 "file": "src/components/auth/Signup.js",
58 "start": {
59 "line": 34,
60 "column": 16
61 },
62 "end": {
63 "line": 37,
64 "column": 3 51 "column": 3
65 } 52 }
66 }, 53 },
@@ -69,11 +56,11 @@
69 "defaultMessage": "!!!Password", 56 "defaultMessage": "!!!Password",
70 "file": "src/components/auth/Signup.js", 57 "file": "src/components/auth/Signup.js",
71 "start": { 58 "start": {
72 "line": 38, 59 "line": 37,
73 "column": 17 60 "column": 17
74 }, 61 },
75 "end": { 62 "end": {
76 "line": 41, 63 "line": 40,
77 "column": 3 64 "column": 3
78 } 65 }
79 }, 66 },
@@ -82,11 +69,11 @@
82 "defaultMessage": "!!!By creating a Franz account you accept the", 69 "defaultMessage": "!!!By creating a Franz account you accept the",
83 "file": "src/components/auth/Signup.js", 70 "file": "src/components/auth/Signup.js",
84 "start": { 71 "start": {
85 "line": 42, 72 "line": 41,
86 "column": 13 73 "column": 13
87 }, 74 },
88 "end": { 75 "end": {
89 "line": 45, 76 "line": 44,
90 "column": 3 77 "column": 3
91 } 78 }
92 }, 79 },
@@ -95,11 +82,11 @@
95 "defaultMessage": "!!!Terms of service", 82 "defaultMessage": "!!!Terms of service",
96 "file": "src/components/auth/Signup.js", 83 "file": "src/components/auth/Signup.js",
97 "start": { 84 "start": {
98 "line": 46, 85 "line": 45,
99 "column": 9 86 "column": 9
100 }, 87 },
101 "end": { 88 "end": {
102 "line": 49, 89 "line": 48,
103 "column": 3 90 "column": 3
104 } 91 }
105 }, 92 },
@@ -108,11 +95,11 @@
108 "defaultMessage": "!!!Privacy Statement", 95 "defaultMessage": "!!!Privacy Statement",
109 "file": "src/components/auth/Signup.js", 96 "file": "src/components/auth/Signup.js",
110 "start": { 97 "start": {
111 "line": 50, 98 "line": 49,
112 "column": 11 99 "column": 11
113 }, 100 },
114 "end": { 101 "end": {
115 "line": 53, 102 "line": 52,
116 "column": 3 103 "column": 3
117 } 104 }
118 }, 105 },
@@ -121,11 +108,11 @@
121 "defaultMessage": "!!!Create account", 108 "defaultMessage": "!!!Create account",
122 "file": "src/components/auth/Signup.js", 109 "file": "src/components/auth/Signup.js",
123 "start": { 110 "start": {
124 "line": 54, 111 "line": 53,
125 "column": 21 112 "column": 21
126 }, 113 },
127 "end": { 114 "end": {
128 "line": 57, 115 "line": 56,
129 "column": 3 116 "column": 3
130 } 117 }
131 }, 118 },
@@ -134,11 +121,11 @@
134 "defaultMessage": "!!!Already have an account, sign in?", 121 "defaultMessage": "!!!Already have an account, sign in?",
135 "file": "src/components/auth/Signup.js", 122 "file": "src/components/auth/Signup.js",
136 "start": { 123 "start": {
137 "line": 58, 124 "line": 57,
138 "column": 13 125 "column": 13
139 }, 126 },
140 "end": { 127 "end": {
141 "line": 61, 128 "line": 60,
142 "column": 3 129 "column": 3
143 } 130 }
144 }, 131 },
@@ -147,11 +134,11 @@
147 "defaultMessage": "!!!A user with that email address already exists", 134 "defaultMessage": "!!!A user with that email address already exists",
148 "file": "src/components/auth/Signup.js", 135 "file": "src/components/auth/Signup.js",
149 "start": { 136 "start": {
150 "line": 62, 137 "line": 61,
151 "column": 18 138 "column": 18
152 }, 139 },
153 "end": { 140 "end": {
154 "line": 65, 141 "line": 64,
155 "column": 3 142 "column": 3
156 } 143 }
157 } 144 }
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 190c5dff7..44cf4fab9 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 26, 7 "line": 28,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 29, 11 "line": 31,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Reload services", 17 "defaultMessage": "!!!Reload services",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 30, 20 "line": 32,
21 "column": 24 21 "column": 24
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 33, 24 "line": 35,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Could not load services and user information", 30 "defaultMessage": "!!!Could not load services and user information",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 34, 33 "line": 36,
34 "column": 26 34 "column": 26
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 37, 37 "line": 39,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/i18n/messages/src/components/services/content/ServiceRestricted.json b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
new file mode 100644
index 000000000..c1984afe3
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "service.restrictedHandler.serviceLimit.headline",
4 "defaultMessage": "!!!You have reached your service limit.",
5 "file": "src/components/services/content/ServiceRestricted.js",
6 "start": {
7 "line": 11,
8 "column": 24
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "service.restrictedHandler.serviceLimit.text",
17 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
18 "file": "src/components/services/content/ServiceRestricted.js",
19 "start": {
20 "line": 15,
21 "column": 20
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 },
28 {
29 "id": "service.restrictedHandler.customUrl.headline",
30 "defaultMessage": "!!!Franz Professional Plan required",
31 "file": "src/components/services/content/ServiceRestricted.js",
32 "start": {
33 "line": 19,
34 "column": 21
35 },
36 "end": {
37 "line": 22,
38 "column": 3
39 }
40 },
41 {
42 "id": "service.restrictedHandler.customUrl.text",
43 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
44 "file": "src/components/services/content/ServiceRestricted.js",
45 "start": {
46 "line": 23,
47 "column": 17
48 },
49 "end": {
50 "line": 26,
51 "column": 3
52 }
53 },
54 {
55 "id": "service.restrictedHandler.action",
56 "defaultMessage": "!!!Upgrade Account",
57 "file": "src/components/services/content/ServiceRestricted.js",
58 "start": {
59 "line": 27,
60 "column": 10
61 },
62 "end": {
63 "line": 30,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json
index 884ab0c90..eb466c0ac 100644
--- a/src/i18n/messages/src/components/services/content/Services.json
+++ b/src/i18n/messages/src/components/services/content/Services.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Welcome to Franz", 4 "defaultMessage": "!!!Welcome to Franz",
5 "file": "src/components/services/content/Services.js", 5 "file": "src/components/services/content/Services.js",
6 "start": { 6 "start": {
7 "line": 11, 7 "line": 14,
8 "column": 11 8 "column": 11
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 14, 11 "line": 17,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Get started", 17 "defaultMessage": "!!!Get started",
18 "file": "src/components/services/content/Services.js", 18 "file": "src/components/services/content/Services.js",
19 "start": { 19 "start": {
20 "line": 15, 20 "line": 18,
21 "column": 14 21 "column": 14
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 18, 24 "line": 21,
25 "column": 3 25 "column": 3
26 } 26 }
27 } 27 }
diff --git a/src/i18n/messages/src/components/settings/account/AccountDashboard.json b/src/i18n/messages/src/components/settings/account/AccountDashboard.json
index 4969db910..06d53e41d 100644
--- a/src/i18n/messages/src/components/settings/account/AccountDashboard.json
+++ b/src/i18n/messages/src/components/settings/account/AccountDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Account", 4 "defaultMessage": "!!!Account",
5 "file": "src/components/settings/account/AccountDashboard.js", 5 "file": "src/components/settings/account/AccountDashboard.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 18,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 21,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,21 +17,8 @@
17 "defaultMessage": "!!!Your Subscription", 17 "defaultMessage": "!!!Your Subscription",
18 "file": "src/components/settings/account/AccountDashboard.js", 18 "file": "src/components/settings/account/AccountDashboard.js",
19 "start": { 19 "start": {
20 "line": 18,
21 "column": 24
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.account.headlineUpgrade",
30 "defaultMessage": "!!!Upgrade your Account",
31 "file": "src/components/settings/account/AccountDashboard.js",
32 "start": {
33 "line": 22, 20 "line": 22,
34 "column": 19 21 "column": 24
35 }, 22 },
36 "end": { 23 "end": {
37 "line": 25, 24 "line": 25,
@@ -65,15 +52,28 @@
65 } 52 }
66 }, 53 },
67 { 54 {
55 "id": "settings.account.upgradeToPro.label",
56 "defaultMessage": "!!!Upgrade to Franz Professional",
57 "file": "src/components/settings/account/AccountDashboard.js",
58 "start": {
59 "line": 34,
60 "column": 23
61 },
62 "end": {
63 "line": 37,
64 "column": 3
65 }
66 },
67 {
68 "id": "settings.account.accountType.basic", 68 "id": "settings.account.accountType.basic",
69 "defaultMessage": "!!!Basic Account", 69 "defaultMessage": "!!!Basic Account",
70 "file": "src/components/settings/account/AccountDashboard.js", 70 "file": "src/components/settings/account/AccountDashboard.js",
71 "start": { 71 "start": {
72 "line": 34, 72 "line": 38,
73 "column": 20 73 "column": 20
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 37, 76 "line": 41,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Premium Supporter Account", 82 "defaultMessage": "!!!Premium Supporter Account",
83 "file": "src/components/settings/account/AccountDashboard.js", 83 "file": "src/components/settings/account/AccountDashboard.js",
84 "start": { 84 "start": {
85 "line": 38, 85 "line": 42,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 41, 89 "line": 45,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Edit Account", 95 "defaultMessage": "!!!Edit Account",
96 "file": "src/components/settings/account/AccountDashboard.js", 96 "file": "src/components/settings/account/AccountDashboard.js",
97 "start": { 97 "start": {
98 "line": 42, 98 "line": 46,
99 "column": 21 99 "column": 21
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 45, 102 "line": 49,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!Invoices", 108 "defaultMessage": "!!Invoices",
109 "file": "src/components/settings/account/AccountDashboard.js", 109 "file": "src/components/settings/account/AccountDashboard.js",
110 "start": { 110 "start": {
111 "line": 46, 111 "line": 50,
112 "column": 18 112 "column": 18
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 49, 115 "line": 53,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Download", 121 "defaultMessage": "!!!Download",
122 "file": "src/components/settings/account/AccountDashboard.js", 122 "file": "src/components/settings/account/AccountDashboard.js",
123 "start": { 123 "start": {
124 "line": 50, 124 "line": 54,
125 "column": 19 125 "column": 19
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 53, 128 "line": 57,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Could not load user information", 134 "defaultMessage": "!!!Could not load user information",
135 "file": "src/components/settings/account/AccountDashboard.js", 135 "file": "src/components/settings/account/AccountDashboard.js",
136 "start": { 136 "start": {
137 "line": 54, 137 "line": 58,
138 "column": 25 138 "column": 25
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 57, 141 "line": 61,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Try again", 147 "defaultMessage": "!!!Try again",
148 "file": "src/components/settings/account/AccountDashboard.js", 148 "file": "src/components/settings/account/AccountDashboard.js",
149 "start": { 149 "start": {
150 "line": 58, 150 "line": 62,
151 "column": 28 151 "column": 28
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 61, 154 "line": 65,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Delete account", 160 "defaultMessage": "!!!Delete account",
161 "file": "src/components/settings/account/AccountDashboard.js", 161 "file": "src/components/settings/account/AccountDashboard.js",
162 "start": { 162 "start": {
163 "line": 62, 163 "line": 66,
164 "column": 17 164 "column": 17
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 65, 167 "line": 69,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.", 173 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.",
174 "file": "src/components/settings/account/AccountDashboard.js", 174 "file": "src/components/settings/account/AccountDashboard.js",
175 "start": { 175 "start": {
176 "line": 66, 176 "line": 70,
177 "column": 14 177 "column": 14
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 69, 180 "line": 73,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,63 @@
186 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", 186 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!",
187 "file": "src/components/settings/account/AccountDashboard.js", 187 "file": "src/components/settings/account/AccountDashboard.js",
188 "start": { 188 "start": {
189 "line": 70, 189 "line": 74,
190 "column": 19 190 "column": 19
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 73, 193 "line": 77,
194 "column": 3
195 }
196 },
197 {
198 "id": "settings.account.trial",
199 "defaultMessage": "!!!Free Trial",
200 "file": "src/components/settings/account/AccountDashboard.js",
201 "start": {
202 "line": 78,
203 "column": 9
204 },
205 "end": {
206 "line": 81,
207 "column": 3
208 }
209 },
210 {
211 "id": "settings.account.yourLicense",
212 "defaultMessage": "!!!Your Franz License:",
213 "file": "src/components/settings/account/AccountDashboard.js",
214 "start": {
215 "line": 82,
216 "column": 15
217 },
218 "end": {
219 "line": 85,
220 "column": 3
221 }
222 },
223 {
224 "id": "settings.account.trialEndsIn",
225 "defaultMessage": "!!!Your free trial ends in {duration}.",
226 "file": "src/components/settings/account/AccountDashboard.js",
227 "start": {
228 "line": 86,
229 "column": 15
230 },
231 "end": {
232 "line": 89,
233 "column": 3
234 }
235 },
236 {
237 "id": "settings.account.trialUpdateBillingInfo",
238 "defaultMessage": "!!!Please update your billing info to continue using {license} after your trial period.",
239 "file": "src/components/settings/account/AccountDashboard.js",
240 "start": {
241 "line": 90,
242 "column": 33
243 },
244 "end": {
245 "line": 93,
194 "column": 3 246 "column": 3
195 } 247 }
196 } 248 }
diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
index 70a989211..7dfb3ce04 100644
--- a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
+++ b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Available services", 4 "defaultMessage": "!!!Available services",
5 "file": "src/components/settings/navigation/SettingsNavigation.js", 5 "file": "src/components/settings/navigation/SettingsNavigation.js",
6 "start": { 6 "start": {
7 "line": 13, 7 "line": 14,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 16, 11 "line": 17,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Your services", 17 "defaultMessage": "!!!Your services",
18 "file": "src/components/settings/navigation/SettingsNavigation.js", 18 "file": "src/components/settings/navigation/SettingsNavigation.js",
19 "start": { 19 "start": {
20 "line": 17, 20 "line": 18,
21 "column": 16 21 "column": 16
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 20, 24 "line": 21,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Your workspaces", 30 "defaultMessage": "!!!Your workspaces",
31 "file": "src/components/settings/navigation/SettingsNavigation.js", 31 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": { 32 "start": {
33 "line": 21, 33 "line": 22,
34 "column": 18 34 "column": 18
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 24, 37 "line": 25,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Account", 43 "defaultMessage": "!!!Account",
44 "file": "src/components/settings/navigation/SettingsNavigation.js", 44 "file": "src/components/settings/navigation/SettingsNavigation.js",
45 "start": { 45 "start": {
46 "line": 25, 46 "line": 26,
47 "column": 11 47 "column": 11
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 28, 50 "line": 29,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Manage Team", 56 "defaultMessage": "!!!Manage Team",
57 "file": "src/components/settings/navigation/SettingsNavigation.js", 57 "file": "src/components/settings/navigation/SettingsNavigation.js",
58 "start": { 58 "start": {
59 "line": 29, 59 "line": 30,
60 "column": 8 60 "column": 8
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 32, 63 "line": 33,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Settings", 69 "defaultMessage": "!!!Settings",
70 "file": "src/components/settings/navigation/SettingsNavigation.js", 70 "file": "src/components/settings/navigation/SettingsNavigation.js",
71 "start": { 71 "start": {
72 "line": 33, 72 "line": 34,
73 "column": 12 73 "column": 12
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 36, 76 "line": 37,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Invite Friends", 82 "defaultMessage": "!!!Invite Friends",
83 "file": "src/components/settings/navigation/SettingsNavigation.js", 83 "file": "src/components/settings/navigation/SettingsNavigation.js",
84 "start": { 84 "start": {
85 "line": 37, 85 "line": 38,
86 "column": 17 86 "column": 17
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 40, 89 "line": 41,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Logout", 95 "defaultMessage": "!!!Logout",
96 "file": "src/components/settings/navigation/SettingsNavigation.js", 96 "file": "src/components/settings/navigation/SettingsNavigation.js",
97 "start": { 97 "start": {
98 "line": 41, 98 "line": 42,
99 "column": 10 99 "column": 10
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 44, 102 "line": 45,
103 "column": 3 103 "column": 3
104 } 104 }
105 } 105 }
diff --git a/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json b/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
index 7d9ed3283..8afaaed50 100644
--- a/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
+++ b/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Available Services", 4 "defaultMessage": "!!!Available Services",
5 "file": "src/components/settings/recipes/RecipesDashboard.js", 5 "file": "src/components/settings/recipes/RecipesDashboard.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 20,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 23,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Search service", 17 "defaultMessage": "!!!Search service",
18 "file": "src/components/settings/recipes/RecipesDashboard.js", 18 "file": "src/components/settings/recipes/RecipesDashboard.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 24,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 27,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Most popular", 30 "defaultMessage": "!!!Most popular",
31 "file": "src/components/settings/recipes/RecipesDashboard.js", 31 "file": "src/components/settings/recipes/RecipesDashboard.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 28,
34 "column": 22 34 "column": 22
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 31,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,24 +43,24 @@
43 "defaultMessage": "!!!All services", 43 "defaultMessage": "!!!All services",
44 "file": "src/components/settings/recipes/RecipesDashboard.js", 44 "file": "src/components/settings/recipes/RecipesDashboard.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 32,
47 "column": 14 47 "column": 14
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 35,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
54 { 54 {
55 "id": "settings.recipes.dev", 55 "id": "settings.recipes.custom",
56 "defaultMessage": "!!!Development", 56 "defaultMessage": "!!!Custom Services",
57 "file": "src/components/settings/recipes/RecipesDashboard.js", 57 "file": "src/components/settings/recipes/RecipesDashboard.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 36,
60 "column": 14 60 "column": 17
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 39,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Sorry, but no service matched your search term.", 69 "defaultMessage": "!!!Sorry, but no service matched your search term.",
70 "file": "src/components/settings/recipes/RecipesDashboard.js", 70 "file": "src/components/settings/recipes/RecipesDashboard.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 40,
73 "column": 16 73 "column": 16
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 43,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Service successfully added", 82 "defaultMessage": "!!!Service successfully added",
83 "file": "src/components/settings/recipes/RecipesDashboard.js", 83 "file": "src/components/settings/recipes/RecipesDashboard.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 44,
86 "column": 31 86 "column": 31
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 47,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,89 @@
95 "defaultMessage": "!!!Missing a service?", 95 "defaultMessage": "!!!Missing a service?",
96 "file": "src/components/settings/recipes/RecipesDashboard.js", 96 "file": "src/components/settings/recipes/RecipesDashboard.js",
97 "start": { 97 "start": {
98 "line": 43, 98 "line": 48,
99 "column": 18 99 "column": 18
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 46, 102 "line": 51,
103 "column": 3
104 }
105 },
106 {
107 "id": "settings.recipes.customService.intro",
108 "defaultMessage": "!!!To add a custom service, copy the recipe folder into:",
109 "file": "src/components/settings/recipes/RecipesDashboard.js",
110 "start": {
111 "line": 52,
112 "column": 21
113 },
114 "end": {
115 "line": 55,
116 "column": 3
117 }
118 },
119 {
120 "id": "settings.recipes.customService.openFolder",
121 "defaultMessage": "!!!Open directory",
122 "file": "src/components/settings/recipes/RecipesDashboard.js",
123 "start": {
124 "line": 56,
125 "column": 14
126 },
127 "end": {
128 "line": 59,
129 "column": 3
130 }
131 },
132 {
133 "id": "settings.recipes.customService.openDevDocs",
134 "defaultMessage": "!!!Developer Documentation",
135 "file": "src/components/settings/recipes/RecipesDashboard.js",
136 "start": {
137 "line": 60,
138 "column": 15
139 },
140 "end": {
141 "line": 63,
142 "column": 3
143 }
144 },
145 {
146 "id": "settings.recipes.customService.headline.customRecipes",
147 "defaultMessage": "!!!Custom Service Recipes",
148 "file": "src/components/settings/recipes/RecipesDashboard.js",
149 "start": {
150 "line": 64,
151 "column": 25
152 },
153 "end": {
154 "line": 67,
155 "column": 3
156 }
157 },
158 {
159 "id": "settings.recipes.customService.headline.communityRecipes",
160 "defaultMessage": "!!!Community Services",
161 "file": "src/components/settings/recipes/RecipesDashboard.js",
162 "start": {
163 "line": 68,
164 "column": 28
165 },
166 "end": {
167 "line": 71,
168 "column": 3
169 }
170 },
171 {
172 "id": "settings.recipes.customService.headline.devRecipes",
173 "defaultMessage": "!!!Your Development Service Recipes",
174 "file": "src/components/settings/recipes/RecipesDashboard.js",
175 "start": {
176 "line": 72,
177 "column": 22
178 },
179 "end": {
180 "line": 75,
103 "column": 3 181 "column": 3
104 } 182 }
105 } 183 }
diff --git a/src/i18n/messages/src/components/settings/services/EditServiceForm.json b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
index 42b741b7a..e66db807d 100644
--- a/src/i18n/messages/src/components/settings/services/EditServiceForm.json
+++ b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Save service", 4 "defaultMessage": "!!!Save service",
5 "file": "src/components/settings/services/EditServiceForm.js", 5 "file": "src/components/settings/services/EditServiceForm.js",
6 "start": { 6 "start": {
7 "line": 22, 7 "line": 24,
8 "column": 15 8 "column": 15
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 25, 11 "line": 27,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Delete Service", 17 "defaultMessage": "!!!Delete Service",
18 "file": "src/components/settings/services/EditServiceForm.js", 18 "file": "src/components/settings/services/EditServiceForm.js",
19 "start": { 19 "start": {
20 "line": 26, 20 "line": 28,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 29, 24 "line": 31,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Available services", 30 "defaultMessage": "!!!Available services",
31 "file": "src/components/settings/services/EditServiceForm.js", 31 "file": "src/components/settings/services/EditServiceForm.js",
32 "start": { 32 "start": {
33 "line": 30, 33 "line": 32,
34 "column": 21 34 "column": 21
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 33, 37 "line": 35,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Your services", 43 "defaultMessage": "!!!Your services",
44 "file": "src/components/settings/services/EditServiceForm.js", 44 "file": "src/components/settings/services/EditServiceForm.js",
45 "start": { 45 "start": {
46 "line": 34, 46 "line": 36,
47 "column": 16 47 "column": 16
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 37, 50 "line": 39,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Add {name}", 56 "defaultMessage": "!!!Add {name}",
57 "file": "src/components/settings/services/EditServiceForm.js", 57 "file": "src/components/settings/services/EditServiceForm.js",
58 "start": { 58 "start": {
59 "line": 38, 59 "line": 40,
60 "column": 22 60 "column": 22
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 41, 63 "line": 43,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Edit {name}", 69 "defaultMessage": "!!!Edit {name}",
70 "file": "src/components/settings/services/EditServiceForm.js", 70 "file": "src/components/settings/services/EditServiceForm.js",
71 "start": { 71 "start": {
72 "line": 42, 72 "line": 44,
73 "column": 23 73 "column": 23
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 45, 76 "line": 47,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Hosted", 82 "defaultMessage": "!!!Hosted",
83 "file": "src/components/settings/services/EditServiceForm.js", 83 "file": "src/components/settings/services/EditServiceForm.js",
84 "start": { 84 "start": {
85 "line": 46, 85 "line": 48,
86 "column": 13 86 "column": 13
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 49, 89 "line": 51,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Self hosted ⭐️", 95 "defaultMessage": "!!!Self hosted ⭐️",
96 "file": "src/components/settings/services/EditServiceForm.js", 96 "file": "src/components/settings/services/EditServiceForm.js",
97 "start": { 97 "start": {
98 "line": 50, 98 "line": 52,
99 "column": 16 99 "column": 16
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 53, 102 "line": 55,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Use the hosted {name} service.", 108 "defaultMessage": "!!!Use the hosted {name} service.",
109 "file": "src/components/settings/services/EditServiceForm.js", 109 "file": "src/components/settings/services/EditServiceForm.js",
110 "start": { 110 "start": {
111 "line": 54, 111 "line": 56,
112 "column": 20 112 "column": 20
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 57, 115 "line": 59,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Could not validate custom {name} server.", 121 "defaultMessage": "!!!Could not validate custom {name} server.",
122 "file": "src/components/settings/services/EditServiceForm.js", 122 "file": "src/components/settings/services/EditServiceForm.js",
123 "start": { 123 "start": {
124 "line": 58, 124 "line": 60,
125 "column": 28 125 "column": 28
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 61, 128 "line": 63,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", 134 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.",
135 "file": "src/components/settings/services/EditServiceForm.js", 135 "file": "src/components/settings/services/EditServiceForm.js",
136 "start": { 136 "start": {
137 "line": 62, 137 "line": 64,
138 "column": 24 138 "column": 24
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 65, 141 "line": 67,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Upgrade your account", 147 "defaultMessage": "!!!Upgrade your account",
148 "file": "src/components/settings/services/EditServiceForm.js", 148 "file": "src/components/settings/services/EditServiceForm.js",
149 "start": { 149 "start": {
150 "line": 66, 150 "line": 68,
151 "column": 27 151 "column": 27
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 69, 154 "line": 71,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 160 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
161 "file": "src/components/settings/services/EditServiceForm.js", 161 "file": "src/components/settings/services/EditServiceForm.js",
162 "start": { 162 "start": {
163 "line": 70, 163 "line": 72,
164 "column": 23 164 "column": 23
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 73, 167 "line": 75,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 173 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
174 "file": "src/components/settings/services/EditServiceForm.js", 174 "file": "src/components/settings/services/EditServiceForm.js",
175 "start": { 175 "start": {
176 "line": 74, 176 "line": 76,
177 "column": 15 177 "column": 15
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 77, 180 "line": 79,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Notifications", 186 "defaultMessage": "!!!Notifications",
187 "file": "src/components/settings/services/EditServiceForm.js", 187 "file": "src/components/settings/services/EditServiceForm.js",
188 "start": { 188 "start": {
189 "line": 78, 189 "line": 80,
190 "column": 25 190 "column": 25
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 81, 193 "line": 83,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Unread message badges", 199 "defaultMessage": "!!!Unread message badges",
200 "file": "src/components/settings/services/EditServiceForm.js", 200 "file": "src/components/settings/services/EditServiceForm.js",
201 "start": { 201 "start": {
202 "line": 82, 202 "line": 84,
203 "column": 18 203 "column": 18
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 85, 206 "line": 87,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!General", 212 "defaultMessage": "!!!General",
213 "file": "src/components/settings/services/EditServiceForm.js", 213 "file": "src/components/settings/services/EditServiceForm.js",
214 "start": { 214 "start": {
215 "line": 86, 215 "line": 88,
216 "column": 19 216 "column": 19
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 89, 219 "line": 91,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Delete", 225 "defaultMessage": "!!!Delete",
226 "file": "src/components/settings/services/EditServiceForm.js", 226 "file": "src/components/settings/services/EditServiceForm.js",
227 "start": { 227 "start": {
228 "line": 90, 228 "line": 92,
229 "column": 14 229 "column": 14
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 93, 232 "line": 95,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Drop your image, or click here", 238 "defaultMessage": "!!!Drop your image, or click here",
239 "file": "src/components/settings/services/EditServiceForm.js", 239 "file": "src/components/settings/services/EditServiceForm.js",
240 "start": { 240 "start": {
241 "line": 94, 241 "line": 96,
242 "column": 14 242 "column": 14
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 97, 245 "line": 99,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 251 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
252 "file": "src/components/settings/services/EditServiceForm.js", 252 "file": "src/components/settings/services/EditServiceForm.js",
253 "start": { 253 "start": {
254 "line": 98, 254 "line": 100,
255 "column": 17 255 "column": 17
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 101, 258 "line": 103,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", 264 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.",
265 "file": "src/components/settings/services/EditServiceForm.js", 265 "file": "src/components/settings/services/EditServiceForm.js",
266 "start": { 266 "start": {
267 "line": 102, 267 "line": 104,
268 "column": 20 268 "column": 20
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 105, 271 "line": 107,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", 277 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.",
278 "file": "src/components/settings/services/EditServiceForm.js", 278 "file": "src/components/settings/services/EditServiceForm.js",
279 "start": { 279 "start": {
280 "line": 106, 280 "line": 108,
281 "column": 13 281 "column": 13
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 109, 284 "line": 111,
285 "column": 3 285 "column": 3
286 } 286 }
287 } 287 }
diff --git a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
index 3803c6512..fa661ea2f 100644
--- a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
+++ b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services", 4 "defaultMessage": "!!!Your services",
5 "file": "src/components/settings/services/ServicesDashboard.js", 5 "file": "src/components/settings/services/ServicesDashboard.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 15,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Search service", 17 "defaultMessage": "!!!Search service",
18 "file": "src/components/settings/services/ServicesDashboard.js", 18 "file": "src/components/settings/services/ServicesDashboard.js",
19 "start": { 19 "start": {
20 "line": 18, 20 "line": 19,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 21, 24 "line": 22,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!You haven't added any services yet.", 30 "defaultMessage": "!!!You haven't added any services yet.",
31 "file": "src/components/settings/services/ServicesDashboard.js", 31 "file": "src/components/settings/services/ServicesDashboard.js",
32 "start": { 32 "start": {
33 "line": 22, 33 "line": 23,
34 "column": 19 34 "column": 19
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 25, 37 "line": 26,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Sorry, but no service matched your search term.", 43 "defaultMessage": "!!!Sorry, but no service matched your search term.",
44 "file": "src/components/settings/services/ServicesDashboard.js", 44 "file": "src/components/settings/services/ServicesDashboard.js",
45 "start": { 45 "start": {
46 "line": 26, 46 "line": 27,
47 "column": 18 47 "column": 18
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 29, 50 "line": 30,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Discover services", 56 "defaultMessage": "!!!Discover services",
57 "file": "src/components/settings/services/ServicesDashboard.js", 57 "file": "src/components/settings/services/ServicesDashboard.js",
58 "start": { 58 "start": {
59 "line": 30, 59 "line": 31,
60 "column": 20 60 "column": 20
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 33, 63 "line": 34,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load your services", 69 "defaultMessage": "!!!Could not load your services",
70 "file": "src/components/settings/services/ServicesDashboard.js", 70 "file": "src/components/settings/services/ServicesDashboard.js",
71 "start": { 71 "start": {
72 "line": 34, 72 "line": 35,
73 "column": 25 73 "column": 25
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 37, 76 "line": 38,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Try again", 82 "defaultMessage": "!!!Try again",
83 "file": "src/components/settings/services/ServicesDashboard.js", 83 "file": "src/components/settings/services/ServicesDashboard.js",
84 "start": { 84 "start": {
85 "line": 38, 85 "line": 39,
86 "column": 21 86 "column": 21
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 41, 89 "line": 42,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Your changes have been saved", 95 "defaultMessage": "!!!Your changes have been saved",
96 "file": "src/components/settings/services/ServicesDashboard.js", 96 "file": "src/components/settings/services/ServicesDashboard.js",
97 "start": { 97 "start": {
98 "line": 42, 98 "line": 43,
99 "column": 15 99 "column": 15
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 45, 102 "line": 46,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Service has been deleted", 108 "defaultMessage": "!!!Service has been deleted",
109 "file": "src/components/settings/services/ServicesDashboard.js", 109 "file": "src/components/settings/services/ServicesDashboard.js",
110 "start": { 110 "start": {
111 "line": 46, 111 "line": 47,
112 "column": 15 112 "column": 15
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 49, 115 "line": 50,
116 "column": 3 116 "column": 3
117 } 117 }
118 } 118 }
diff --git a/src/i18n/messages/src/components/subscription/SubscriptionForm.json b/src/i18n/messages/src/components/subscription/SubscriptionForm.json
index f98eb986f..6d235254e 100644
--- a/src/i18n/messages/src/components/subscription/SubscriptionForm.json
+++ b/src/i18n/messages/src/components/subscription/SubscriptionForm.json
@@ -1,183 +1,53 @@
1[ 1[
2 { 2 {
3 "id": "subscription.submit.label", 3 "id": "subscription.cta.choosePlan",
4 "defaultMessage": "!!!Support the development of Franz", 4 "defaultMessage": "!!!Choose your plan",
5 "file": "src/components/subscription/SubscriptionForm.js", 5 "file": "src/components/subscription/SubscriptionForm.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 13,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.paymentSessionError",
17 "defaultMessage": "!!!Could not initialize payment form",
18 "file": "src/components/subscription/SubscriptionForm.js",
19 "start": {
20 "line": 18,
21 "column": 23
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "subscription.type.free",
30 "defaultMessage": "!!!free",
31 "file": "src/components/subscription/SubscriptionForm.js",
32 "start": {
33 "line": 22,
34 "column": 12
35 },
36 "end": {
37 "line": 25,
38 "column": 3 12 "column": 3
39 } 13 }
40 }, 14 },
41 { 15 {
42 "id": "subscription.type.month", 16 "id": "settings.account.headlineUpgradeAccount",
43 "defaultMessage": "!!!month", 17 "defaultMessage": "!!!Upgrade your account and get the full Franz experience",
44 "file": "src/components/subscription/SubscriptionForm.js", 18 "file": "src/components/subscription/SubscriptionForm.js",
45 "start": { 19 "start": {
46 "line": 26, 20 "line": 17,
47 "column": 15 21 "column": 18
48 }, 22 },
49 "end": { 23 "end": {
50 "line": 29, 24 "line": 20,
51 "column": 3 25 "column": 3
52 } 26 }
53 }, 27 },
54 { 28 {
55 "id": "subscription.type.year", 29 "id": "subscription.teaser.intro",
56 "defaultMessage": "!!!year", 30 "defaultMessage": "!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
57 "file": "src/components/subscription/SubscriptionForm.js", 31 "file": "src/components/subscription/SubscriptionForm.js",
58 "start": { 32 "start": {
59 "line": 30, 33 "line": 21,
60 "column": 14 34 "column": 14
61 }, 35 },
62 "end": { 36 "end": {
63 "line": 33, 37 "line": 24,
64 "column": 3 38 "column": 3
65 } 39 }
66 }, 40 },
67 { 41 {
68 "id": "subscription.includedFeatures", 42 "id": "subscription.teaser.includedFeatures",
69 "defaultMessage": "!!!The Franz Premium Supporter Account includes", 43 "defaultMessage": "!!!Paid Franz Plans include:",
70 "file": "src/components/subscription/SubscriptionForm.js", 44 "file": "src/components/subscription/SubscriptionForm.js",
71 "start": { 45 "start": {
72 "line": 34, 46 "line": 25,
73 "column": 20 47 "column": 20
74 }, 48 },
75 "end": { 49 "end": {
76 "line": 37, 50 "line": 28,
77 "column": 3
78 }
79 },
80 {
81 "id": "subscription.features.onpremise.mattermost",
82 "defaultMessage": "!!!Add on-premise/hosted services like Mattermost",
83 "file": "src/components/subscription/SubscriptionForm.js",
84 "start": {
85 "line": 38,
86 "column": 13
87 },
88 "end": {
89 "line": 41,
90 "column": 3
91 }
92 },
93 {
94 "id": "subscription.features.noInterruptions",
95 "defaultMessage": "!!!No app delays & nagging to upgrade license",
96 "file": "src/components/subscription/SubscriptionForm.js",
97 "start": {
98 "line": 42,
99 "column": 19
100 },
101 "end": {
102 "line": 45,
103 "column": 3
104 }
105 },
106 {
107 "id": "subscription.features.proxy",
108 "defaultMessage": "!!!Proxy support for services",
109 "file": "src/components/subscription/SubscriptionForm.js",
110 "start": {
111 "line": 46,
112 "column": 9
113 },
114 "end": {
115 "line": 49,
116 "column": 3
117 }
118 },
119 {
120 "id": "subscription.features.spellchecker",
121 "defaultMessage": "!!!Support for Spellchecker",
122 "file": "src/components/subscription/SubscriptionForm.js",
123 "start": {
124 "line": 50,
125 "column": 16
126 },
127 "end": {
128 "line": 53,
129 "column": 3
130 }
131 },
132 {
133 "id": "subscription.features.workspaces",
134 "defaultMessage": "!!!Organize your services in workspaces",
135 "file": "src/components/subscription/SubscriptionForm.js",
136 "start": {
137 "line": 54,
138 "column": 14
139 },
140 "end": {
141 "line": 57,
142 "column": 3
143 }
144 },
145 {
146 "id": "subscription.features.ads",
147 "defaultMessage": "!!!No ads, ever!",
148 "file": "src/components/subscription/SubscriptionForm.js",
149 "start": {
150 "line": 58,
151 "column": 7
152 },
153 "end": {
154 "line": 61,
155 "column": 3
156 }
157 },
158 {
159 "id": "subscription.features.comingSoon",
160 "defaultMessage": "!!!coming soon",
161 "file": "src/components/subscription/SubscriptionForm.js",
162 "start": {
163 "line": 62,
164 "column": 14
165 },
166 "end": {
167 "line": 65,
168 "column": 3
169 }
170 },
171 {
172 "id": "subscription.euTaxInfo",
173 "defaultMessage": "!!!EU residents: local sales tax may apply",
174 "file": "src/components/subscription/SubscriptionForm.js",
175 "start": {
176 "line": 66,
177 "column": 13
178 },
179 "end": {
180 "line": 69,
181 "column": 3 51 "column": 3
182 } 52 }
183 } 53 }
diff --git a/src/i18n/messages/src/components/subscription/TrialForm.json b/src/i18n/messages/src/components/subscription/TrialForm.json
new file mode 100644
index 000000000..8b387ba36
--- /dev/null
+++ b/src/i18n/messages/src/components/subscription/TrialForm.json
@@ -0,0 +1,93 @@
1[
2 {
3 "id": "subscription.cta.activateTrial",
4 "defaultMessage": "!!!Yes, start the free Franz Professional trial",
5 "file": "src/components/subscription/TrialForm.js",
6 "start": {
7 "line": 14,
8 "column": 21
9 },
10 "end": {
11 "line": 17,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.cta.allOptions",
17 "defaultMessage": "!!!See all options",
18 "file": "src/components/subscription/TrialForm.js",
19 "start": {
20 "line": 18,
21 "column": 20
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.account.headlineTrialUpgrade",
30 "defaultMessage": "!!!Get the free 14 day Franz Professional Trial",
31 "file": "src/components/subscription/TrialForm.js",
32 "start": {
33 "line": 22,
34 "column": 18
35 },
36 "end": {
37 "line": 25,
38 "column": 3
39 }
40 },
41 {
42 "id": "subscription.includedProFeatures",
43 "defaultMessage": "!!!The Franz Professional Plan includes:",
44 "file": "src/components/subscription/TrialForm.js",
45 "start": {
46 "line": 26,
47 "column": 20
48 },
49 "end": {
50 "line": 29,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.trial.terms.headline",
56 "defaultMessage": "!!!No strings attached",
57 "file": "src/components/subscription/TrialForm.js",
58 "start": {
59 "line": 30,
60 "column": 29
61 },
62 "end": {
63 "line": 33,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.trial.terms.noCreditCard",
69 "defaultMessage": "!!!No credit card required",
70 "file": "src/components/subscription/TrialForm.js",
71 "start": {
72 "line": 34,
73 "column": 16
74 },
75 "end": {
76 "line": 37,
77 "column": 3
78 }
79 },
80 {
81 "id": "pricing.trial.terms.automaticTrialEnd",
82 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
83 "file": "src/components/subscription/TrialForm.js",
84 "start": {
85 "line": 38,
86 "column": 21
87 },
88 "end": {
89 "line": 41,
90 "column": 3
91 }
92 }
93] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json
new file mode 100644
index 000000000..497e299a4
--- /dev/null
+++ b/src/i18n/messages/src/components/ui/FeatureList.json
@@ -0,0 +1,132 @@
1[
2 {
3 "id": "pricing.features.unlimitedServices",
4 "defaultMessage": "!!!Add unlimited services",
5 "file": "src/components/ui/FeatureList.js",
6 "start": {
7 "line": 8,
8 "column": 21
9 },
10 "end": {
11 "line": 11,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.features.spellchecker",
17 "defaultMessage": "!!!Spellchecker support",
18 "file": "src/components/ui/FeatureList.js",
19 "start": {
20 "line": 12,
21 "column": 16
22 },
23 "end": {
24 "line": 15,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.features.workspaces",
30 "defaultMessage": "!!!Workspaces",
31 "file": "src/components/ui/FeatureList.js",
32 "start": {
33 "line": 16,
34 "column": 14
35 },
36 "end": {
37 "line": 19,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.features.customWebsites",
43 "defaultMessage": "!!!Add Custom Websites",
44 "file": "src/components/ui/FeatureList.js",
45 "start": {
46 "line": 20,
47 "column": 18
48 },
49 "end": {
50 "line": 23,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.features.onPremise",
56 "defaultMessage": "!!!On-premise & other Hosted Services",
57 "file": "src/components/ui/FeatureList.js",
58 "start": {
59 "line": 24,
60 "column": 13
61 },
62 "end": {
63 "line": 27,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.features.thirdPartyServices",
69 "defaultMessage": "!!!Install 3rd party services",
70 "file": "src/components/ui/FeatureList.js",
71 "start": {
72 "line": 28,
73 "column": 22
74 },
75 "end": {
76 "line": 31,
77 "column": 3
78 }
79 },
80 {
81 "id": "pricing.features.serviceProxies",
82 "defaultMessage": "!!!Service Proxies",
83 "file": "src/components/ui/FeatureList.js",
84 "start": {
85 "line": 32,
86 "column": 18
87 },
88 "end": {
89 "line": 35,
90 "column": 3
91 }
92 },
93 {
94 "id": "pricing.features.teamManagement",
95 "defaultMessage": "!!!Team Management",
96 "file": "src/components/ui/FeatureList.js",
97 "start": {
98 "line": 36,
99 "column": 18
100 },
101 "end": {
102 "line": 39,
103 "column": 3
104 }
105 },
106 {
107 "id": "pricing.features.appDelays",
108 "defaultMessage": "!!!No Waiting Screens",
109 "file": "src/components/ui/FeatureList.js",
110 "start": {
111 "line": 40,
112 "column": 13
113 },
114 "end": {
115 "line": 43,
116 "column": 3
117 }
118 },
119 {
120 "id": "pricing.features.adFree",
121 "defaultMessage": "!!!Forever ad-free",
122 "file": "src/components/ui/FeatureList.js",
123 "start": {
124 "line": 44,
125 "column": 10
126 },
127 "end": {
128 "line": 47,
129 "column": 3
130 }
131 }
132] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/delayApp/Component.json b/src/i18n/messages/src/features/delayApp/Component.json
index bacd9444a..0d345a47b 100644
--- a/src/i18n/messages/src/features/delayApp/Component.json
+++ b/src/i18n/messages/src/features/delayApp/Component.json
@@ -4,24 +4,50 @@
4 "defaultMessage": "!!!Please purchase license to skip waiting", 4 "defaultMessage": "!!!Please purchase license to skip waiting",
5 "file": "src/features/delayApp/Component.js", 5 "file": "src/features/delayApp/Component.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 17,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 20,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
15 { 15 {
16 "id": "feature.delayApp.action", 16 "id": "feature.delayApp.trial.headline",
17 "defaultMessage": "!!!Get the free Franz Professional 14 day trial and skip the line",
18 "file": "src/features/delayApp/Component.js",
19 "start": {
20 "line": 21,
21 "column": 17
22 },
23 "end": {
24 "line": 24,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.delayApp.upgrade.action",
17 "defaultMessage": "!!!Get a Franz Supporter License", 30 "defaultMessage": "!!!Get a Franz Supporter License",
18 "file": "src/features/delayApp/Component.js", 31 "file": "src/features/delayApp/Component.js",
19 "start": { 32 "start": {
20 "line": 19, 33 "line": 25,
21 "column": 10 34 "column": 10
22 }, 35 },
23 "end": { 36 "end": {
24 "line": 22, 37 "line": 28,
38 "column": 3
39 }
40 },
41 {
42 "id": "feature.delayApp.trial.action",
43 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
44 "file": "src/features/delayApp/Component.js",
45 "start": {
46 "line": 29,
47 "column": 15
48 },
49 "end": {
50 "line": 32,
25 "column": 3 51 "column": 3
26 } 52 }
27 }, 53 },
@@ -30,11 +56,11 @@
30 "defaultMessage": "!!!Franz will continue in {seconds} seconds.", 56 "defaultMessage": "!!!Franz will continue in {seconds} seconds.",
31 "file": "src/features/delayApp/Component.js", 57 "file": "src/features/delayApp/Component.js",
32 "start": { 58 "start": {
33 "line": 23, 59 "line": 33,
34 "column": 8 60 "column": 8
35 }, 61 },
36 "end": { 62 "end": {
37 "line": 26, 63 "line": 36,
38 "column": 3 64 "column": 3
39 } 65 }
40 } 66 }
diff --git a/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json b/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json
new file mode 100644
index 000000000..e6e3cef99
--- /dev/null
+++ b/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "feature.announcements.changelog.headline",
4 "defaultMessage": "!!!Changes in Franz {version}",
5 "file": "src/features/serviceLimit/components/AnnouncementScreen.js",
6 "start": {
7 "line": 20,
8 "column": 12
9 },
10 "end": {
11 "line": 23,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json b/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json
new file mode 100644
index 000000000..df5bc03e8
--- /dev/null
+++ b/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "feature.serviceLimit.limitReached",
4 "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.",
5 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
6 "start": {
7 "line": 11,
8 "column": 16
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "premiumFeature.button.upgradeAccount",
17 "defaultMessage": "!!!Upgrade account",
18 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
19 "start": {
20 "line": 15,
21 "column": 10
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/shareFranz/Component.json b/src/i18n/messages/src/features/shareFranz/Component.json
index 34a43d5a0..79b425b15 100644
--- a/src/i18n/messages/src/features/shareFranz/Component.json
+++ b/src/i18n/messages/src/features/shareFranz/Component.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Franz is better together!", 4 "defaultMessage": "!!!Franz is better together!",
5 "file": "src/features/shareFranz/Component.js", 5 "file": "src/features/shareFranz/Component.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 16,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 19,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.", 17 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
18 "file": "src/features/shareFranz/Component.js", 18 "file": "src/features/shareFranz/Component.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 20,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 23,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Share as email", 30 "defaultMessage": "!!!Share as email",
31 "file": "src/features/shareFranz/Component.js", 31 "file": "src/features/shareFranz/Component.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 24,
34 "column": 16 34 "column": 16
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 27,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Share on Facebook", 43 "defaultMessage": "!!!Share on Facebook",
44 "file": "src/features/shareFranz/Component.js", 44 "file": "src/features/shareFranz/Component.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 28,
47 "column": 19 47 "column": 19
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 31,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Share on Twitter", 56 "defaultMessage": "!!!Share on Twitter",
57 "file": "src/features/shareFranz/Component.js", 57 "file": "src/features/shareFranz/Component.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 32,
60 "column": 18 60 "column": 18
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 35,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", 69 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
70 "file": "src/features/shareFranz/Component.js", 70 "file": "src/features/shareFranz/Component.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 36,
73 "column": 18 73 "column": 18
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 39,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger", 82 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
83 "file": "src/features/shareFranz/Component.js", 83 "file": "src/features/shareFranz/Component.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 40,
86 "column": 20 86 "column": 20
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 43,
90 "column": 3 90 "column": 3
91 } 91 }
92 } 92 }
diff --git a/src/i18n/messages/src/helpers/plan-helpers.json b/src/i18n/messages/src/helpers/plan-helpers.json
new file mode 100644
index 000000000..df8ee19e3
--- /dev/null
+++ b/src/i18n/messages/src/helpers/plan-helpers.json
@@ -0,0 +1,54 @@
1[
2 {
3 "id": "pricing.plan.pro",
4 "defaultMessage": "!!!Franz Professional",
5 "file": "src/helpers/plan-helpers.js",
6 "start": {
7 "line": 5,
8 "column": 15
9 },
10 "end": {
11 "line": 8,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.plan.personal",
17 "defaultMessage": "!!!Franz Personal",
18 "file": "src/helpers/plan-helpers.js",
19 "start": {
20 "line": 9,
21 "column": 20
22 },
23 "end": {
24 "line": 12,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.plan.free",
30 "defaultMessage": "!!!Franz Free",
31 "file": "src/helpers/plan-helpers.js",
32 "start": {
33 "line": 13,
34 "column": 16
35 },
36 "end": {
37 "line": 16,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.plan.legacy",
43 "defaultMessage": "!!!Franz Premium",
44 "file": "src/helpers/plan-helpers.js",
45 "start": {
46 "line": 17,
47 "column": 18
48 },
49 "end": {
50 "line": 20,
51 "column": 3
52 }
53 }
54] \ No newline at end of file
diff --git a/src/i18n/messages/src/helpers/pricing-helpers.json b/src/i18n/messages/src/helpers/pricing-helpers.json
new file mode 100644
index 000000000..4030a3e3b
--- /dev/null
+++ b/src/i18n/messages/src/helpers/pricing-helpers.json
@@ -0,0 +1,80 @@
1[
2 {
3 "id": "pricing.plan.pro-yearly",
4 "defaultMessage": "!!!Franz Professional Yearly",
5 "file": "src/helpers/pricing-helpers.js",
6 "start": {
7 "line": 5,
8 "column": 22
9 },
10 "end": {
11 "line": 8,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.plan.pro-monthly",
17 "defaultMessage": "!!!Franz Professional Monthly",
18 "file": "src/helpers/pricing-helpers.js",
19 "start": {
20 "line": 9,
21 "column": 23
22 },
23 "end": {
24 "line": 12,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.plan.personal-yearly",
30 "defaultMessage": "!!!Franz Personal Yearly",
31 "file": "src/helpers/pricing-helpers.js",
32 "start": {
33 "line": 13,
34 "column": 27
35 },
36 "end": {
37 "line": 16,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.plan.personal-monthly",
43 "defaultMessage": "!!!Franz Personal Monthly",
44 "file": "src/helpers/pricing-helpers.js",
45 "start": {
46 "line": 17,
47 "column": 28
48 },
49 "end": {
50 "line": 20,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.plan.free",
56 "defaultMessage": "!!!Franz Free",
57 "file": "src/helpers/pricing-helpers.js",
58 "start": {
59 "line": 21,
60 "column": 16
61 },
62 "end": {
63 "line": 24,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.plan.legacy",
69 "defaultMessage": "!!!Franz Premium",
70 "file": "src/helpers/pricing-helpers.js",
71 "start": {
72 "line": 25,
73 "column": 18
74 },
75 "end": {
76 "line": 28,
77 "column": 3
78 }
79 }
80] \ No newline at end of file
diff --git a/src/i18n/messages/src/lib/Menu.json b/src/i18n/messages/src/lib/Menu.json
index daafb0900..cee46608c 100644
--- a/src/i18n/messages/src/lib/Menu.json
+++ b/src/i18n/messages/src/lib/Menu.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Edit", 4 "defaultMessage": "!!!Edit",
5 "file": "src/lib/Menu.js", 5 "file": "src/lib/Menu.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 18,
8 "column": 8 8 "column": 8
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 21,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Undo", 17 "defaultMessage": "!!!Undo",
18 "file": "src/lib/Menu.js", 18 "file": "src/lib/Menu.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 22,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 25,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Redo", 30 "defaultMessage": "!!!Redo",
31 "file": "src/lib/Menu.js", 31 "file": "src/lib/Menu.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 26,
34 "column": 8 34 "column": 8
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 29,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Cut", 43 "defaultMessage": "!!!Cut",
44 "file": "src/lib/Menu.js", 44 "file": "src/lib/Menu.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 30,
47 "column": 7 47 "column": 7
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 33,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Copy", 56 "defaultMessage": "!!!Copy",
57 "file": "src/lib/Menu.js", 57 "file": "src/lib/Menu.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 34,
60 "column": 8 60 "column": 8
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 37,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Paste", 69 "defaultMessage": "!!!Paste",
70 "file": "src/lib/Menu.js", 70 "file": "src/lib/Menu.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 38,
73 "column": 9 73 "column": 9
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 41,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Paste And Match Style", 82 "defaultMessage": "!!!Paste And Match Style",
83 "file": "src/lib/Menu.js", 83 "file": "src/lib/Menu.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 42,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 45,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Delete", 95 "defaultMessage": "!!!Delete",
96 "file": "src/lib/Menu.js", 96 "file": "src/lib/Menu.js",
97 "start": { 97 "start": {
98 "line": 43, 98 "line": 46,
99 "column": 10 99 "column": 10
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 46, 102 "line": 49,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Select All", 108 "defaultMessage": "!!!Select All",
109 "file": "src/lib/Menu.js", 109 "file": "src/lib/Menu.js",
110 "start": { 110 "start": {
111 "line": 47, 111 "line": 50,
112 "column": 13 112 "column": 13
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 50, 115 "line": 53,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Speech", 121 "defaultMessage": "!!!Speech",
122 "file": "src/lib/Menu.js", 122 "file": "src/lib/Menu.js",
123 "start": { 123 "start": {
124 "line": 51, 124 "line": 54,
125 "column": 10 125 "column": 10
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 54, 128 "line": 57,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Start Speaking", 134 "defaultMessage": "!!!Start Speaking",
135 "file": "src/lib/Menu.js", 135 "file": "src/lib/Menu.js",
136 "start": { 136 "start": {
137 "line": 55, 137 "line": 58,
138 "column": 17 138 "column": 17
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 58, 141 "line": 61,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Stop Speaking", 147 "defaultMessage": "!!!Stop Speaking",
148 "file": "src/lib/Menu.js", 148 "file": "src/lib/Menu.js",
149 "start": { 149 "start": {
150 "line": 59, 150 "line": 62,
151 "column": 16 151 "column": 16
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 62, 154 "line": 65,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Start Dictation", 160 "defaultMessage": "!!!Start Dictation",
161 "file": "src/lib/Menu.js", 161 "file": "src/lib/Menu.js",
162 "start": { 162 "start": {
163 "line": 63, 163 "line": 66,
164 "column": 18 164 "column": 18
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 66, 167 "line": 69,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!Emoji & Symbols", 173 "defaultMessage": "!!!Emoji & Symbols",
174 "file": "src/lib/Menu.js", 174 "file": "src/lib/Menu.js",
175 "start": { 175 "start": {
176 "line": 67, 176 "line": 70,
177 "column": 16 177 "column": 16
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 70, 180 "line": 73,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Actual Size", 186 "defaultMessage": "!!!Actual Size",
187 "file": "src/lib/Menu.js", 187 "file": "src/lib/Menu.js",
188 "start": { 188 "start": {
189 "line": 71, 189 "line": 74,
190 "column": 13 190 "column": 13
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 74, 193 "line": 77,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Zoom In", 199 "defaultMessage": "!!!Zoom In",
200 "file": "src/lib/Menu.js", 200 "file": "src/lib/Menu.js",
201 "start": { 201 "start": {
202 "line": 75, 202 "line": 78,
203 "column": 10 203 "column": 10
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 78, 206 "line": 81,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!Zoom Out", 212 "defaultMessage": "!!!Zoom Out",
213 "file": "src/lib/Menu.js", 213 "file": "src/lib/Menu.js",
214 "start": { 214 "start": {
215 "line": 79, 215 "line": 82,
216 "column": 11 216 "column": 11
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 82, 219 "line": 85,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Enter Full Screen", 225 "defaultMessage": "!!!Enter Full Screen",
226 "file": "src/lib/Menu.js", 226 "file": "src/lib/Menu.js",
227 "start": { 227 "start": {
228 "line": 83, 228 "line": 86,
229 "column": 19 229 "column": 19
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 86, 232 "line": 89,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Exit Full Screen", 238 "defaultMessage": "!!!Exit Full Screen",
239 "file": "src/lib/Menu.js", 239 "file": "src/lib/Menu.js",
240 "start": { 240 "start": {
241 "line": 87, 241 "line": 90,
242 "column": 18 242 "column": 18
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 90, 245 "line": 93,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!Toggle Full Screen", 251 "defaultMessage": "!!!Toggle Full Screen",
252 "file": "src/lib/Menu.js", 252 "file": "src/lib/Menu.js",
253 "start": { 253 "start": {
254 "line": 91, 254 "line": 94,
255 "column": 20 255 "column": 20
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 94, 258 "line": 97,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,24 @@
264 "defaultMessage": "!!!Toggle Developer Tools", 264 "defaultMessage": "!!!Toggle Developer Tools",
265 "file": "src/lib/Menu.js", 265 "file": "src/lib/Menu.js",
266 "start": { 266 "start": {
267 "line": 95, 267 "line": 98,
268 "column": 18 268 "column": 18
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 98, 271 "line": 101,
272 "column": 3
273 }
274 },
275 {
276 "id": "menu.view.toggleTodosDevTools",
277 "defaultMessage": "!!!Toggle Todos Developer Tools",
278 "file": "src/lib/Menu.js",
279 "start": {
280 "line": 102,
281 "column": 23
282 },
283 "end": {
284 "line": 105,
272 "column": 3 285 "column": 3
273 } 286 }
274 }, 287 },
@@ -277,11 +290,11 @@
277 "defaultMessage": "!!!Toggle Service Developer Tools", 290 "defaultMessage": "!!!Toggle Service Developer Tools",
278 "file": "src/lib/Menu.js", 291 "file": "src/lib/Menu.js",
279 "start": { 292 "start": {
280 "line": 99, 293 "line": 106,
281 "column": 25 294 "column": 25
282 }, 295 },
283 "end": { 296 "end": {
284 "line": 102, 297 "line": 109,
285 "column": 3 298 "column": 3
286 } 299 }
287 }, 300 },
@@ -290,11 +303,11 @@
290 "defaultMessage": "!!!Reload Service", 303 "defaultMessage": "!!!Reload Service",
291 "file": "src/lib/Menu.js", 304 "file": "src/lib/Menu.js",
292 "start": { 305 "start": {
293 "line": 103, 306 "line": 110,
294 "column": 17 307 "column": 17
295 }, 308 },
296 "end": { 309 "end": {
297 "line": 106, 310 "line": 113,
298 "column": 3 311 "column": 3
299 } 312 }
300 }, 313 },
@@ -303,11 +316,11 @@
303 "defaultMessage": "!!!Reload Franz", 316 "defaultMessage": "!!!Reload Franz",
304 "file": "src/lib/Menu.js", 317 "file": "src/lib/Menu.js",
305 "start": { 318 "start": {
306 "line": 107, 319 "line": 114,
307 "column": 15 320 "column": 15
308 }, 321 },
309 "end": { 322 "end": {
310 "line": 110, 323 "line": 117,
311 "column": 3 324 "column": 3
312 } 325 }
313 }, 326 },
@@ -316,11 +329,11 @@
316 "defaultMessage": "!!!Minimize", 329 "defaultMessage": "!!!Minimize",
317 "file": "src/lib/Menu.js", 330 "file": "src/lib/Menu.js",
318 "start": { 331 "start": {
319 "line": 111, 332 "line": 118,
320 "column": 12 333 "column": 12
321 }, 334 },
322 "end": { 335 "end": {
323 "line": 114, 336 "line": 121,
324 "column": 3 337 "column": 3
325 } 338 }
326 }, 339 },
@@ -329,11 +342,11 @@
329 "defaultMessage": "!!!Close", 342 "defaultMessage": "!!!Close",
330 "file": "src/lib/Menu.js", 343 "file": "src/lib/Menu.js",
331 "start": { 344 "start": {
332 "line": 115, 345 "line": 122,
333 "column": 9 346 "column": 9
334 }, 347 },
335 "end": { 348 "end": {
336 "line": 118, 349 "line": 125,
337 "column": 3 350 "column": 3
338 } 351 }
339 }, 352 },
@@ -342,11 +355,11 @@
342 "defaultMessage": "!!!Learn More", 355 "defaultMessage": "!!!Learn More",
343 "file": "src/lib/Menu.js", 356 "file": "src/lib/Menu.js",
344 "start": { 357 "start": {
345 "line": 119, 358 "line": 126,
346 "column": 13 359 "column": 13
347 }, 360 },
348 "end": { 361 "end": {
349 "line": 122, 362 "line": 129,
350 "column": 3 363 "column": 3
351 } 364 }
352 }, 365 },
@@ -355,11 +368,11 @@
355 "defaultMessage": "!!!Changelog", 368 "defaultMessage": "!!!Changelog",
356 "file": "src/lib/Menu.js", 369 "file": "src/lib/Menu.js",
357 "start": { 370 "start": {
358 "line": 123, 371 "line": 130,
359 "column": 13 372 "column": 13
360 }, 373 },
361 "end": { 374 "end": {
362 "line": 126, 375 "line": 133,
363 "column": 3 376 "column": 3
364 } 377 }
365 }, 378 },
@@ -368,11 +381,50 @@
368 "defaultMessage": "!!!Support", 381 "defaultMessage": "!!!Support",
369 "file": "src/lib/Menu.js", 382 "file": "src/lib/Menu.js",
370 "start": { 383 "start": {
371 "line": 127, 384 "line": 134,
372 "column": 11 385 "column": 11
373 }, 386 },
374 "end": { 387 "end": {
375 "line": 130, 388 "line": 137,
389 "column": 3
390 }
391 },
392 {
393 "id": "menu.help.debugInfo",
394 "defaultMessage": "!!!Copy Debug Information",
395 "file": "src/lib/Menu.js",
396 "start": {
397 "line": 138,
398 "column": 13
399 },
400 "end": {
401 "line": 141,
402 "column": 3
403 }
404 },
405 {
406 "id": "menu.help.debugInfoCopiedHeadline",
407 "defaultMessage": "!!!Franz Debug Information",
408 "file": "src/lib/Menu.js",
409 "start": {
410 "line": 142,
411 "column": 27
412 },
413 "end": {
414 "line": 145,
415 "column": 3
416 }
417 },
418 {
419 "id": "menu.help.debugInfoCopiedBody",
420 "defaultMessage": "!!!Your Debug Information has been copied to your clipboard.",
421 "file": "src/lib/Menu.js",
422 "start": {
423 "line": 146,
424 "column": 23
425 },
426 "end": {
427 "line": 149,
376 "column": 3 428 "column": 3
377 } 429 }
378 }, 430 },
@@ -381,11 +433,11 @@
381 "defaultMessage": "!!!Terms of Service", 433 "defaultMessage": "!!!Terms of Service",
382 "file": "src/lib/Menu.js", 434 "file": "src/lib/Menu.js",
383 "start": { 435 "start": {
384 "line": 131, 436 "line": 150,
385 "column": 7 437 "column": 7
386 }, 438 },
387 "end": { 439 "end": {
388 "line": 134, 440 "line": 153,
389 "column": 3 441 "column": 3
390 } 442 }
391 }, 443 },
@@ -394,11 +446,11 @@
394 "defaultMessage": "!!!Privacy Statement", 446 "defaultMessage": "!!!Privacy Statement",
395 "file": "src/lib/Menu.js", 447 "file": "src/lib/Menu.js",
396 "start": { 448 "start": {
397 "line": 135, 449 "line": 154,
398 "column": 11 450 "column": 11
399 }, 451 },
400 "end": { 452 "end": {
401 "line": 138, 453 "line": 157,
402 "column": 3 454 "column": 3
403 } 455 }
404 }, 456 },
@@ -407,11 +459,11 @@
407 "defaultMessage": "!!!File", 459 "defaultMessage": "!!!File",
408 "file": "src/lib/Menu.js", 460 "file": "src/lib/Menu.js",
409 "start": { 461 "start": {
410 "line": 139, 462 "line": 158,
411 "column": 8 463 "column": 8
412 }, 464 },
413 "end": { 465 "end": {
414 "line": 142, 466 "line": 161,
415 "column": 3 467 "column": 3
416 } 468 }
417 }, 469 },
@@ -420,11 +472,11 @@
420 "defaultMessage": "!!!View", 472 "defaultMessage": "!!!View",
421 "file": "src/lib/Menu.js", 473 "file": "src/lib/Menu.js",
422 "start": { 474 "start": {
423 "line": 143, 475 "line": 162,
424 "column": 8 476 "column": 8
425 }, 477 },
426 "end": { 478 "end": {
427 "line": 146, 479 "line": 165,
428 "column": 3 480 "column": 3
429 } 481 }
430 }, 482 },
@@ -433,11 +485,11 @@
433 "defaultMessage": "!!!Services", 485 "defaultMessage": "!!!Services",
434 "file": "src/lib/Menu.js", 486 "file": "src/lib/Menu.js",
435 "start": { 487 "start": {
436 "line": 147, 488 "line": 166,
437 "column": 12 489 "column": 12
438 }, 490 },
439 "end": { 491 "end": {
440 "line": 150, 492 "line": 169,
441 "column": 3 493 "column": 3
442 } 494 }
443 }, 495 },
@@ -446,11 +498,11 @@
446 "defaultMessage": "!!!Window", 498 "defaultMessage": "!!!Window",
447 "file": "src/lib/Menu.js", 499 "file": "src/lib/Menu.js",
448 "start": { 500 "start": {
449 "line": 151, 501 "line": 170,
450 "column": 10 502 "column": 10
451 }, 503 },
452 "end": { 504 "end": {
453 "line": 154, 505 "line": 173,
454 "column": 3 506 "column": 3
455 } 507 }
456 }, 508 },
@@ -459,11 +511,11 @@
459 "defaultMessage": "!!!Help", 511 "defaultMessage": "!!!Help",
460 "file": "src/lib/Menu.js", 512 "file": "src/lib/Menu.js",
461 "start": { 513 "start": {
462 "line": 155, 514 "line": 174,
463 "column": 8 515 "column": 8
464 }, 516 },
465 "end": { 517 "end": {
466 "line": 158, 518 "line": 177,
467 "column": 3 519 "column": 3
468 } 520 }
469 }, 521 },
@@ -472,11 +524,11 @@
472 "defaultMessage": "!!!About Franz", 524 "defaultMessage": "!!!About Franz",
473 "file": "src/lib/Menu.js", 525 "file": "src/lib/Menu.js",
474 "start": { 526 "start": {
475 "line": 159, 527 "line": 178,
476 "column": 9 528 "column": 9
477 }, 529 },
478 "end": { 530 "end": {
479 "line": 162, 531 "line": 181,
480 "column": 3 532 "column": 3
481 } 533 }
482 }, 534 },
@@ -485,11 +537,11 @@
485 "defaultMessage": "!!!What's new?", 537 "defaultMessage": "!!!What's new?",
486 "file": "src/lib/Menu.js", 538 "file": "src/lib/Menu.js",
487 "start": { 539 "start": {
488 "line": 163, 540 "line": 182,
489 "column": 16 541 "column": 16
490 }, 542 },
491 "end": { 543 "end": {
492 "line": 166, 544 "line": 185,
493 "column": 3 545 "column": 3
494 } 546 }
495 }, 547 },
@@ -498,11 +550,11 @@
498 "defaultMessage": "!!!Settings", 550 "defaultMessage": "!!!Settings",
499 "file": "src/lib/Menu.js", 551 "file": "src/lib/Menu.js",
500 "start": { 552 "start": {
501 "line": 167, 553 "line": 186,
502 "column": 12 554 "column": 12
503 }, 555 },
504 "end": { 556 "end": {
505 "line": 170, 557 "line": 189,
506 "column": 3 558 "column": 3
507 } 559 }
508 }, 560 },
@@ -511,11 +563,11 @@
511 "defaultMessage": "!!!Check for updates", 563 "defaultMessage": "!!!Check for updates",
512 "file": "src/lib/Menu.js", 564 "file": "src/lib/Menu.js",
513 "start": { 565 "start": {
514 "line": 171, 566 "line": 190,
515 "column": 19 567 "column": 19
516 }, 568 },
517 "end": { 569 "end": {
518 "line": 174, 570 "line": 193,
519 "column": 3 571 "column": 3
520 } 572 }
521 }, 573 },
@@ -524,11 +576,11 @@
524 "defaultMessage": "!!!Hide", 576 "defaultMessage": "!!!Hide",
525 "file": "src/lib/Menu.js", 577 "file": "src/lib/Menu.js",
526 "start": { 578 "start": {
527 "line": 175, 579 "line": 194,
528 "column": 8 580 "column": 8
529 }, 581 },
530 "end": { 582 "end": {
531 "line": 178, 583 "line": 197,
532 "column": 3 584 "column": 3
533 } 585 }
534 }, 586 },
@@ -537,11 +589,11 @@
537 "defaultMessage": "!!!Hide Others", 589 "defaultMessage": "!!!Hide Others",
538 "file": "src/lib/Menu.js", 590 "file": "src/lib/Menu.js",
539 "start": { 591 "start": {
540 "line": 179, 592 "line": 198,
541 "column": 14 593 "column": 14
542 }, 594 },
543 "end": { 595 "end": {
544 "line": 182, 596 "line": 201,
545 "column": 3 597 "column": 3
546 } 598 }
547 }, 599 },
@@ -550,11 +602,11 @@
550 "defaultMessage": "!!!Unhide", 602 "defaultMessage": "!!!Unhide",
551 "file": "src/lib/Menu.js", 603 "file": "src/lib/Menu.js",
552 "start": { 604 "start": {
553 "line": 183, 605 "line": 202,
554 "column": 10 606 "column": 10
555 }, 607 },
556 "end": { 608 "end": {
557 "line": 186, 609 "line": 205,
558 "column": 3 610 "column": 3
559 } 611 }
560 }, 612 },
@@ -563,11 +615,11 @@
563 "defaultMessage": "!!!Quit", 615 "defaultMessage": "!!!Quit",
564 "file": "src/lib/Menu.js", 616 "file": "src/lib/Menu.js",
565 "start": { 617 "start": {
566 "line": 187, 618 "line": 206,
567 "column": 8 619 "column": 8
568 }, 620 },
569 "end": { 621 "end": {
570 "line": 190, 622 "line": 209,
571 "column": 3 623 "column": 3
572 } 624 }
573 }, 625 },
@@ -576,11 +628,11 @@
576 "defaultMessage": "!!!Add New Service...", 628 "defaultMessage": "!!!Add New Service...",
577 "file": "src/lib/Menu.js", 629 "file": "src/lib/Menu.js",
578 "start": { 630 "start": {
579 "line": 191, 631 "line": 210,
580 "column": 17 632 "column": 17
581 }, 633 },
582 "end": { 634 "end": {
583 "line": 194, 635 "line": 213,
584 "column": 3 636 "column": 3
585 } 637 }
586 }, 638 },
@@ -589,11 +641,11 @@
589 "defaultMessage": "!!!Add New Workspace...", 641 "defaultMessage": "!!!Add New Workspace...",
590 "file": "src/lib/Menu.js", 642 "file": "src/lib/Menu.js",
591 "start": { 643 "start": {
592 "line": 195, 644 "line": 214,
593 "column": 19 645 "column": 19
594 }, 646 },
595 "end": { 647 "end": {
596 "line": 198, 648 "line": 217,
597 "column": 3 649 "column": 3
598 } 650 }
599 }, 651 },
@@ -602,11 +654,11 @@
602 "defaultMessage": "!!!Open workspace drawer", 654 "defaultMessage": "!!!Open workspace drawer",
603 "file": "src/lib/Menu.js", 655 "file": "src/lib/Menu.js",
604 "start": { 656 "start": {
605 "line": 199, 657 "line": 218,
606 "column": 23 658 "column": 23
607 }, 659 },
608 "end": { 660 "end": {
609 "line": 202, 661 "line": 221,
610 "column": 3 662 "column": 3
611 } 663 }
612 }, 664 },
@@ -615,11 +667,11 @@
615 "defaultMessage": "!!!Close workspace drawer", 667 "defaultMessage": "!!!Close workspace drawer",
616 "file": "src/lib/Menu.js", 668 "file": "src/lib/Menu.js",
617 "start": { 669 "start": {
618 "line": 203, 670 "line": 222,
619 "column": 24 671 "column": 24
620 }, 672 },
621 "end": { 673 "end": {
622 "line": 206, 674 "line": 225,
623 "column": 3 675 "column": 3
624 } 676 }
625 }, 677 },
@@ -628,11 +680,11 @@
628 "defaultMessage": "!!!Activate next service...", 680 "defaultMessage": "!!!Activate next service...",
629 "file": "src/lib/Menu.js", 681 "file": "src/lib/Menu.js",
630 "start": { 682 "start": {
631 "line": 207, 683 "line": 226,
632 "column": 23 684 "column": 23
633 }, 685 },
634 "end": { 686 "end": {
635 "line": 210, 687 "line": 229,
636 "column": 3 688 "column": 3
637 } 689 }
638 }, 690 },
@@ -641,11 +693,11 @@
641 "defaultMessage": "!!!Activate previous service...", 693 "defaultMessage": "!!!Activate previous service...",
642 "file": "src/lib/Menu.js", 694 "file": "src/lib/Menu.js",
643 "start": { 695 "start": {
644 "line": 211, 696 "line": 230,
645 "column": 27 697 "column": 27
646 }, 698 },
647 "end": { 699 "end": {
648 "line": 214, 700 "line": 233,
649 "column": 3 701 "column": 3
650 } 702 }
651 }, 703 },
@@ -654,11 +706,11 @@
654 "defaultMessage": "!!!Disable notifications & audio", 706 "defaultMessage": "!!!Disable notifications & audio",
655 "file": "src/lib/Menu.js", 707 "file": "src/lib/Menu.js",
656 "start": { 708 "start": {
657 "line": 215, 709 "line": 234,
658 "column": 11 710 "column": 11
659 }, 711 },
660 "end": { 712 "end": {
661 "line": 218, 713 "line": 237,
662 "column": 3 714 "column": 3
663 } 715 }
664 }, 716 },
@@ -667,11 +719,11 @@
667 "defaultMessage": "!!!Enable notifications & audio", 719 "defaultMessage": "!!!Enable notifications & audio",
668 "file": "src/lib/Menu.js", 720 "file": "src/lib/Menu.js",
669 "start": { 721 "start": {
670 "line": 219, 722 "line": 238,
671 "column": 13 723 "column": 13
672 }, 724 },
673 "end": { 725 "end": {
674 "line": 222, 726 "line": 241,
675 "column": 3 727 "column": 3
676 } 728 }
677 }, 729 },
@@ -680,11 +732,11 @@
680 "defaultMessage": "!!!Workspaces", 732 "defaultMessage": "!!!Workspaces",
681 "file": "src/lib/Menu.js", 733 "file": "src/lib/Menu.js",
682 "start": { 734 "start": {
683 "line": 223, 735 "line": 242,
684 "column": 14 736 "column": 14
685 }, 737 },
686 "end": { 738 "end": {
687 "line": 226, 739 "line": 245,
688 "column": 3 740 "column": 3
689 } 741 }
690 }, 742 },
@@ -693,11 +745,50 @@
693 "defaultMessage": "!!!Default", 745 "defaultMessage": "!!!Default",
694 "file": "src/lib/Menu.js", 746 "file": "src/lib/Menu.js",
695 "start": { 747 "start": {
696 "line": 227, 748 "line": 246,
697 "column": 20 749 "column": 20
698 }, 750 },
699 "end": { 751 "end": {
700 "line": 230, 752 "line": 249,
753 "column": 3
754 }
755 },
756 {
757 "id": "menu.todos",
758 "defaultMessage": "!!!Todos",
759 "file": "src/lib/Menu.js",
760 "start": {
761 "line": 250,
762 "column": 9
763 },
764 "end": {
765 "line": 253,
766 "column": 3
767 }
768 },
769 {
770 "id": "menu.Todoss.openTodosDrawer",
771 "defaultMessage": "!!!Open Todos drawer",
772 "file": "src/lib/Menu.js",
773 "start": {
774 "line": 254,
775 "column": 19
776 },
777 "end": {
778 "line": 257,
779 "column": 3
780 }
781 },
782 {
783 "id": "menu.Todoss.closeTodosDrawer",
784 "defaultMessage": "!!!Close Todos drawer",
785 "file": "src/lib/Menu.js",
786 "start": {
787 "line": 258,
788 "column": 20
789 },
790 "end": {
791 "line": 261,
701 "column": 3 792 "column": 3
702 } 793 }
703 } 794 }
diff --git a/src/index.html b/src/index.html
index bf15e2d4e..198c1ab7b 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,6 +10,7 @@
10 <div class="window-draggable"></div> 10 <div class="window-draggable"></div>
11 <div class="dev-warning">DEV MODE</div> 11 <div class="dev-warning">DEV MODE</div>
12 <div id="root"></div> 12 <div id="root"></div>
13 <div id="portalContainer"></div>
13 <script> 14 <script>
14 document.querySelector('body').classList.add(process.env.OS_PLATFORM ? process.env.OS_PLATFORM : process.platform); 15 document.querySelector('body').classList.add(process.env.OS_PLATFORM ? process.env.OS_PLATFORM : process.platform);
15 16
@@ -34,11 +35,16 @@
34 const originalReloadBehaviour = window._onLiveReloadFileChanged; 35 const originalReloadBehaviour = window._onLiveReloadFileChanged;
35 36
36 window._onLiveReloadFileChanged = (file) => { 37 window._onLiveReloadFileChanged = (file) => {
37 if (!file.path.includes('/build/webview/') && !file.path.includes('/build/index.js') && !file.path.includes('/build/electron/')) { 38 const isTodoPreloadPath = file.path.includes('/build/features/todos/preload.js');
39 if (!file.path.includes('/build/webview/') && !file.path.includes('/build/index.js') && !file.path.includes('/build/electron/') && !isTodoPreloadPath) {
38 originalReloadBehaviour(file); 40 originalReloadBehaviour(file);
39 } else { 41 } else {
40 if (file.path.includes('/build/webview/')) { 42 if (isTodoPreloadPath) {
41 debug('Livereload: Reloading all webvies'); 43 debug('Livereload: Reloading all webviews');
44 const webview = document.querySelector('webview[partition="persist:todos"]');
45 if (webview) webview.reload();
46 } else if (file.path.includes('/build/webview/')) {
47 debug('Livereload: Reloading all webviews');
42 const webviews = document.querySelectorAll('webview').forEach(webview => webview.reload()); 48 const webviews = document.querySelectorAll('webview').forEach(webview => webview.reload());
43 } else { 49 } else {
44 debug('Livereload: skip reload as only main process files have changed'); 50 debug('Livereload: skip reload as only main process files have changed');
diff --git a/src/index.js b/src/index.js
index 55592c328..d9d51fd5b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -105,35 +105,6 @@ if (!gotTheLock) {
105 } 105 }
106 }); 106 });
107} 107}
108// const isSecondInstance = app.makeSingleInstance((argv) => {
109// if (mainWindow) {
110// if (mainWindow.isMinimized()) mainWindow.restore();
111// mainWindow.focus();
112
113// if (process.platform === 'win32') {
114// // Keep only command line / deep linked arguments
115// const url = argv.slice(1);
116
117// if (url) {
118// handleDeepLink(mainWindow, url.toString());
119// }
120// }
121// }
122
123// if (argv.includes('--reset-window')) {
124// // Needs to be delayed to not interfere with mainWindow.restore();
125// setTimeout(() => {
126// debug('Resetting windows via Task');
127// mainWindow.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100);
128// mainWindow.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height);
129// }, 1);
130// }
131// });
132
133// if (isSecondInstance) {
134// console.log('An instance of Franz is already running. Exiting...');
135// app.exit();
136// }
137 108
138// Fix Unity indicator issue 109// Fix Unity indicator issue
139// https://github.com/electron/electron/issues/9046 110// https://github.com/electron/electron/issues/9046
diff --git a/src/lib/Menu.js b/src/lib/Menu.js
index e0dfd736e..81efaf18f 100644
--- a/src/lib/Menu.js
+++ b/src/lib/Menu.js
@@ -1,4 +1,4 @@
1import { remote, shell } from 'electron'; 1import { remote, shell, clipboard } from 'electron';
2import { observable, autorun } from 'mobx'; 2import { observable, autorun } from 'mobx';
3import { defineMessages } from 'react-intl'; 3import { defineMessages } from 'react-intl';
4 4
@@ -8,6 +8,9 @@ import { workspaceActions } from '../features/workspaces/actions';
8import { gaEvent } from './analytics'; 8import { gaEvent } from './analytics';
9import { announcementActions } from '../features/announcements/actions'; 9import { announcementActions } from '../features/announcements/actions';
10import { announcementsStore } from '../features/announcements'; 10import { announcementsStore } from '../features/announcements';
11import TodoStore from '../features/todos/store';
12import { GA_CATEGORY_TODOS, todosStore } from '../features/todos';
13import { todoActions } from '../features/todos/actions';
11 14
12const { app, Menu, dialog } = remote; 15const { app, Menu, dialog } = remote;
13 16
@@ -96,6 +99,10 @@ const menuItems = defineMessages({
96 id: 'menu.view.toggleDevTools', 99 id: 'menu.view.toggleDevTools',
97 defaultMessage: '!!!Toggle Developer Tools', 100 defaultMessage: '!!!Toggle Developer Tools',
98 }, 101 },
102 toggleTodosDevTools: {
103 id: 'menu.view.toggleTodosDevTools',
104 defaultMessage: '!!!Toggle Todos Developer Tools',
105 },
99 toggleServiceDevTools: { 106 toggleServiceDevTools: {
100 id: 'menu.view.toggleServiceDevTools', 107 id: 'menu.view.toggleServiceDevTools',
101 defaultMessage: '!!!Toggle Service Developer Tools', 108 defaultMessage: '!!!Toggle Service Developer Tools',
@@ -128,6 +135,18 @@ const menuItems = defineMessages({
128 id: 'menu.help.support', 135 id: 'menu.help.support',
129 defaultMessage: '!!!Support', 136 defaultMessage: '!!!Support',
130 }, 137 },
138 debugInfo: {
139 id: 'menu.help.debugInfo',
140 defaultMessage: '!!!Copy Debug Information',
141 },
142 debugInfoCopiedHeadline: {
143 id: 'menu.help.debugInfoCopiedHeadline',
144 defaultMessage: '!!!Franz Debug Information',
145 },
146 debugInfoCopiedBody: {
147 id: 'menu.help.debugInfoCopiedBody',
148 defaultMessage: '!!!Your Debug Information has been copied to your clipboard.',
149 },
131 tos: { 150 tos: {
132 id: 'menu.help.tos', 151 id: 'menu.help.tos',
133 defaultMessage: '!!!Terms of Service', 152 defaultMessage: '!!!Terms of Service',
@@ -228,6 +247,18 @@ const menuItems = defineMessages({
228 id: 'menu.workspaces.defaultWorkspace', 247 id: 'menu.workspaces.defaultWorkspace',
229 defaultMessage: '!!!Default', 248 defaultMessage: '!!!Default',
230 }, 249 },
250 todos: {
251 id: 'menu.todos',
252 defaultMessage: '!!!Todos',
253 },
254 openTodosDrawer: {
255 id: 'menu.Todoss.openTodosDrawer',
256 defaultMessage: '!!!Open Todos drawer',
257 },
258 closeTodosDrawer: {
259 id: 'menu.Todoss.closeTodosDrawer',
260 defaultMessage: '!!!Close Todos drawer',
261 },
231}); 262});
232 263
233function getActiveWebview() { 264function getActiveWebview() {
@@ -336,6 +367,11 @@ const _templateFactory = intl => [
336 visible: workspaceStore.isFeatureEnabled, 367 visible: workspaceStore.isFeatureEnabled,
337 }, 368 },
338 { 369 {
370 label: intl.formatMessage(menuItems.todos),
371 submenu: [],
372 visible: todosStore.isFeatureEnabled,
373 },
374 {
339 label: intl.formatMessage(menuItems.window), 375 label: intl.formatMessage(menuItems.window),
340 role: 'window', 376 role: 'window',
341 submenu: [ 377 submenu: [
@@ -608,6 +644,17 @@ export default class FranzMenu {
608 enabled: this.stores.user.isLoggedIn && this.stores.services.enabled.length > 0, 644 enabled: this.stores.user.isLoggedIn && this.stores.services.enabled.length > 0,
609 }); 645 });
610 646
647 if (this.stores.features.features.isTodosEnabled) {
648 tpl[1].submenu.push({
649 label: intl.formatMessage(menuItems.toggleTodosDevTools),
650 accelerator: `${cmdKey}+Shift+Alt+O`,
651 click: () => {
652 const webview = document.querySelector('webview[partition="persist:todos"]');
653 if (webview) webview.openDevTools();
654 },
655 });
656 }
657
611 tpl[1].submenu.unshift({ 658 tpl[1].submenu.unshift({
612 label: intl.formatMessage(menuItems.reloadService), 659 label: intl.formatMessage(menuItems.reloadService),
613 id: 'reloadService', // TODO: needed? 660 id: 'reloadService', // TODO: needed?
@@ -760,6 +807,14 @@ export default class FranzMenu {
760 tpl[4].submenu = this.workspacesMenu(); 807 tpl[4].submenu = this.workspacesMenu();
761 } 808 }
762 809
810 if (todosStore.isFeatureEnabled) {
811 tpl[5].submenu = this.todosMenu();
812 }
813
814 tpl[tpl.length - 1].submenu.push({
815 type: 'separator',
816 }, this.debugMenu());
817
763 this.currentTemplate = tpl; 818 this.currentTemplate = tpl;
764 const menu = Menu.buildFromTemplate(tpl); 819 const menu = Menu.buildFromTemplate(tpl);
765 Menu.setApplicationMenu(menu); 820 Menu.setApplicationMenu(menu);
@@ -870,6 +925,53 @@ export default class FranzMenu {
870 return menu; 925 return menu;
871 } 926 }
872 927
928 todosMenu() {
929 const { isTodosPanelVisible } = TodoStore;
930 const { intl } = window.franz;
931 const menu = [];
932
933 // Open todos drawer:
934 const drawerLabel = (
935 isTodosPanelVisible ? menuItems.closeTodosDrawer : menuItems.openTodosDrawer
936 );
937 menu.push({
938 label: intl.formatMessage(drawerLabel),
939 accelerator: `${cmdKey}+T`,
940 click: () => {
941 todoActions.toggleTodosPanel();
942 gaEvent(GA_CATEGORY_TODOS, 'toggleDrawer', 'menu');
943 },
944 enabled: this.stores.user.isLoggedIn,
945 }, {
946 type: 'separator',
947 });
948
949 return menu;
950 }
951
952
953 debugMenu() {
954 const { intl } = window.franz;
955
956 return {
957 label: intl.formatMessage(menuItems.debugInfo),
958 click: () => {
959 const { debugInfo } = this.stores.app;
960
961 clipboard.write({
962 text: JSON.stringify(debugInfo),
963 });
964
965 this.actions.app.notify({
966 title: intl.formatMessage(menuItems.debugInfoCopiedHeadline),
967 options: {
968 body: intl.formatMessage(menuItems.debugInfoCopiedBody),
969 },
970 });
971 },
972 };
973 }
974
873 _getServiceName(service) { 975 _getServiceName(service) {
874 if (service.name) { 976 if (service.name) {
875 return service.name; 977 return service.name;
diff --git a/src/models/Service.js b/src/models/Service.js
index 88bce3360..848a84aa2 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -4,6 +4,11 @@ import normalizeUrl from 'normalize-url';
4 4
5const debug = require('debug')('Franz:Service'); 5const debug = require('debug')('Franz:Service');
6 6
7export const RESTRICTION_TYPES = {
8 SERVICE_LIMIT: 0,
9 CUSTOM_URL: 1,
10};
11
7export default class Service { 12export default class Service {
8 id = ''; 13 id = '';
9 14
@@ -59,6 +64,12 @@ export default class Service {
59 64
60 @observable errorMessage = ''; 65 @observable errorMessage = '';
61 66
67 @observable isUsingCustomUrl = false;
68
69 @observable isServiceAccessRestricted = false;
70
71 @observable restrictionType = null;
72
62 constructor(data, recipe) { 73 constructor(data, recipe) {
63 if (!data) { 74 if (!data) {
64 console.error('Service config not valid'); 75 console.error('Service config not valid');
@@ -111,6 +122,10 @@ export default class Service {
111 this.unreadDirectMessageCount = 0; 122 this.unreadDirectMessageCount = 0;
112 this.unreadIndirectMessageCount = 0; 123 this.unreadIndirectMessageCount = 0;
113 } 124 }
125
126 if (this.recipe.hasCustomUrl && this.customUrl) {
127 this.isUsingCustomUrl = true;
128 }
114 }); 129 });
115 } 130 }
116 131
diff --git a/src/models/User.js b/src/models/User.js
index bec78fc16..0a2b1f62a 100644
--- a/src/models/User.js
+++ b/src/models/User.js
@@ -20,6 +20,10 @@ export default class User {
20 20
21 @observable isSubscriptionOwner = false; 21 @observable isSubscriptionOwner = false;
22 22
23 @observable hasSubscription = false;
24
25 @observable hadSubscription = false;
26
23 @observable isPremium = false; 27 @observable isPremium = false;
24 28
25 @observable beta = false; 29 @observable beta = false;
@@ -32,6 +36,9 @@ export default class User {
32 36
33 @observable locale = false; 37 @observable locale = false;
34 38
39 @observable team = {};
40
41
35 constructor(data) { 42 constructor(data) {
36 if (!data.id) { 43 if (!data.id) {
37 throw Error('User requires Id'); 44 throw Error('User requires Id');
@@ -47,8 +54,13 @@ export default class User {
47 this.beta = data.beta || this.beta; 54 this.beta = data.beta || this.beta;
48 this.donor = data.donor || this.donor; 55 this.donor = data.donor || this.donor;
49 this.isDonor = data.isDonor || this.isDonor; 56 this.isDonor = data.isDonor || this.isDonor;
50 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
51 this.isMiner = data.isMiner || this.isMiner; 57 this.isMiner = data.isMiner || this.isMiner;
52 this.locale = data.locale || this.locale; 58 this.locale = data.locale || this.locale;
59
60 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
61 this.hasSubscription = data.hasSubscription || this.hasSubscription;
62 this.hadSubscription = data.hadSubscription || this.hadSubscription;
63
64 this.team = data.team || this.team;
53 } 65 }
54} 66}
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 72c4b4d0b..6054e6721 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -8,6 +8,9 @@ import AutoLaunch from 'auto-launch';
8import prettyBytes from 'pretty-bytes'; 8import prettyBytes from 'pretty-bytes';
9import ms from 'ms'; 9import ms from 'ms';
10import { URL } from 'url'; 10import { URL } from 'url';
11import os from 'os';
12import path from 'path';
13import { readJsonSync } from 'fs-extra';
11 14
12import Store from './lib/Store'; 15import Store from './lib/Store';
13import Request from './lib/Request'; 16import Request from './lib/Request';
@@ -23,7 +26,7 @@ import { isValidExternalURL } from '../helpers/url-helpers';
23 26
24const debug = require('debug')('Franz:AppStore'); 27const debug = require('debug')('Franz:AppStore');
25 28
26const { app, systemPreferences } = remote; 29const { app, systemPreferences, screen } = remote;
27 30
28const mainWindow = remote.getCurrentWindow(); 31const mainWindow = remote.getCurrentWindow();
29 32
@@ -182,6 +185,26 @@ export default class AppStore extends Store {
182 return prettyBytes(this.getAppCacheSizeRequest.execute().result || 0); 185 return prettyBytes(this.getAppCacheSizeRequest.execute().result || 0);
183 } 186 }
184 187
188 @computed get debugInfo() {
189 return {
190 host: {
191 platform: process.platform,
192 release: os.release(),
193 screens: screen.getAllDisplays(),
194 },
195 franz: {
196 version: app.getVersion(),
197 electron: process.versions.electron,
198 installedRecipes: this.stores.recipes.all.map(recipe => ({ id: recipe.id, version: recipe.version })),
199 devRecipes: this.stores.recipePreviews.dev.map(recipe => ({ id: recipe.id, version: recipe.version })),
200 services: this.stores.services.all.map(service => ({ id: service.id, recipe: service.recipe.id })),
201 workspaces: this.stores.workspaces.workspaces.map(workspace => ({ id: workspace.id, services: workspace.services })),
202 windowSettings: readJsonSync(path.join(app.getPath('userData'), 'window-state.json')),
203 user: this.stores.user.data.id,
204 },
205 };
206 }
207
185 // Actions 208 // Actions
186 @action _notify({ 209 @action _notify({
187 title, options, notificationId, serviceId = null, 210 title, options, notificationId, serviceId = null,
@@ -189,7 +212,7 @@ export default class AppStore extends Store {
189 if (this.stores.settings.all.app.isAppMuted) return; 212 if (this.stores.settings.all.app.isAppMuted) return;
190 213
191 // TODO: is there a simple way to use blobs for notifications without storing them on disk? 214 // TODO: is there a simple way to use blobs for notifications without storing them on disk?
192 if (options.icon.startsWith('blob:')) { 215 if (options.icon && options.icon.startsWith('blob:')) {
193 delete options.icon; 216 delete options.icon;
194 } 217 }
195 218
@@ -334,6 +357,8 @@ export default class AppStore extends Store {
334 this.locale = this._getDefaultLocale(); 357 this.locale = this._getDefaultLocale();
335 } 358 }
336 359
360 moment.locale(this.locale);
361
337 debug(`Set locale to "${this.locale}"`); 362 debug(`Set locale to "${this.locale}"`);
338 } 363 }
339 364
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index e7832088b..cf28b6bec 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -16,6 +16,9 @@ import workspaces from '../features/workspaces';
16import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
17import announcements from '../features/announcements'; 17import announcements from '../features/announcements';
18import settingsWS from '../features/settingsWS'; 18import settingsWS from '../features/settingsWS';
19import serviceLimit from '../features/serviceLimit';
20import communityRecipes from '../features/communityRecipes';
21import todos from '../features/todos';
19 22
20import { DEFAULT_FEATURES_CONFIG } from '../config'; 23import { DEFAULT_FEATURES_CONFIG } from '../config';
21 24
@@ -75,5 +78,8 @@ export default class FeaturesStore extends Store {
75 shareFranz(this.stores, this.actions); 78 shareFranz(this.stores, this.actions);
76 announcements(this.stores, this.actions); 79 announcements(this.stores, this.actions);
77 settingsWS(this.stores, this.actions); 80 settingsWS(this.stores, this.actions);
81 serviceLimit(this.stores, this.actions);
82 communityRecipes(this.stores, this.actions);
83 todos(this.stores, this.actions);
78 } 84 }
79} 85}
diff --git a/src/stores/RecipesStore.js b/src/stores/RecipesStore.js
index ab64bf79c..d51192078 100644
--- a/src/stores/RecipesStore.js
+++ b/src/stores/RecipesStore.js
@@ -20,6 +20,11 @@ export default class RecipesStore extends Store {
20 // Register action handlers 20 // Register action handlers
21 this.actions.recipe.install.listen(this._install.bind(this)); 21 this.actions.recipe.install.listen(this._install.bind(this));
22 this.actions.recipe.update.listen(this._update.bind(this)); 22 this.actions.recipe.update.listen(this._update.bind(this));
23
24 // Reactions
25 this.registerReactions([
26 this._checkIfRecipeIsInstalled.bind(this),
27 ]);
23 } 28 }
24 29
25 setup() { 30 setup() {
@@ -99,4 +104,26 @@ export default class RecipesStore extends Store {
99 syncUpdate(0); 104 syncUpdate(0);
100 } 105 }
101 } 106 }
107
108 async _checkIfRecipeIsInstalled() {
109 const { router } = this.stores;
110
111 const match = matchRoute('/settings/services/add/:id', router.location.pathname);
112 if (match) {
113 const recipeId = match.id;
114
115 if (!this.stores.recipes.isInstalled(recipeId)) {
116 router.push('/settings/recipes');
117 debug(`Recipe ${recipeId} is not installed, trying to install it`);
118
119 const recipe = await this.installRecipeRequest.execute(recipeId)._promise;
120 if (recipe) {
121 await this.allRecipesRequest.invalidate({ immediately: true })._promise;
122 router.push(`/settings/services/add/${recipeId}`);
123 } else {
124 router.push('/settings/recipes');
125 }
126 }
127 }
128 }
102} 129}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index d63302fce..2fc543192 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -13,6 +13,8 @@ import CachedRequest from './lib/CachedRequest';
13import { matchRoute } from '../helpers/routing-helpers'; 13import { matchRoute } from '../helpers/routing-helpers';
14import { gaEvent, statsEvent } from '../lib/analytics'; 14import { gaEvent, statsEvent } from '../lib/analytics';
15import { workspaceStore } from '../features/workspaces'; 15import { workspaceStore } from '../features/workspaces';
16import { serviceLimitStore } from '../features/serviceLimit';
17import { RESTRICTION_TYPES } from '../models/Service';
16 18
17const debug = require('debug')('Franz:ServiceStore'); 19const debug = require('debug')('Franz:ServiceStore');
18 20
@@ -75,6 +77,7 @@ export default class ServicesStore extends Store {
75 this._saveActiveService.bind(this), 77 this._saveActiveService.bind(this),
76 this._logoutReaction.bind(this), 78 this._logoutReaction.bind(this),
77 this._handleMuteSettings.bind(this), 79 this._handleMuteSettings.bind(this),
80 this._restrictServiceAccess.bind(this),
78 ]); 81 ]);
79 82
80 // Just bind this 83 // Just bind this
@@ -98,7 +101,10 @@ export default class ServicesStore extends Store {
98 if (this.stores.user.isLoggedIn) { 101 if (this.stores.user.isLoggedIn) {
99 const services = this.allServicesRequest.execute().result; 102 const services = this.allServicesRequest.execute().result;
100 if (services) { 103 if (services) {
101 return observable(services.slice().slice().sort((a, b) => a.order - b.order)); 104 return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => {
105 s.index = index;
106 return s;
107 }));
102 } 108 }
103 } 109 }
104 return []; 110 return [];
@@ -148,22 +154,13 @@ export default class ServicesStore extends Store {
148 } 154 }
149 155
150 async _showAddServiceInterface({ recipeId }) { 156 async _showAddServiceInterface({ recipeId }) {
151 const recipesStore = this.stores.recipes; 157 this.stores.router.push(`/settings/services/add/${recipeId}`);
152
153 if (recipesStore.isInstalled(recipeId)) {
154 debug(`Recipe ${recipeId} is installed`);
155 this._redirectToAddServiceRoute(recipeId);
156 } else {
157 debug(`Recipe ${recipeId} is not installed`);
158 // We access the RecipeStore action directly
159 // returns Promise instead of action
160 await this.stores.recipes._install({ recipeId });
161 this._redirectToAddServiceRoute(recipeId);
162 }
163 } 158 }
164 159
165 // Actions 160 // Actions
166 @action async _createService({ recipeId, serviceData, redirect = true }) { 161 @action async _createService({ recipeId, serviceData, redirect = true }) {
162 if (serviceLimitStore.userHasReachedServiceLimit) return;
163
167 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 164 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
168 165
169 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 166 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
@@ -451,6 +448,9 @@ export default class ServicesStore extends Store {
451 redirect: false, 448 redirect: false,
452 }); 449 });
453 } 450 }
451 } else if (channel === 'feature:todos') {
452 Object.assign(args[0].data, { serviceId });
453 this.actions.todos.handleHostMessage(args[0]);
454 } 454 }
455 } 455 }
456 456
@@ -689,12 +689,36 @@ export default class ServicesStore extends Store {
689 return serviceData; 689 return serviceData;
690 } 690 }
691 691
692 // Helper 692 _restrictServiceAccess() {
693 _redirectToAddServiceRoute(recipeId) { 693 const { features } = this.stores.features;
694 const route = `/settings/services/add/${recipeId}`; 694 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
695 this.stores.router.push(route); 695
696 this.all.map((service, index) => {
697 if (userHasReachedServiceLimit) {
698 service.isServiceAccessRestricted = index >= serviceLimit;
699
700 if (service.isServiceAccessRestricted) {
701 service.restrictionType = RESTRICTION_TYPES.SERVICE_LIMIT;
702
703 debug('Restricting access to server due to service limit');
704 }
705 }
706
707 if (service.isUsingCustomUrl) {
708 service.isServiceAccessRestricted = !features.isCustomUrlIncludedInCurrentPlan;
709
710 if (service.isServiceAccessRestricted) {
711 service.restrictionType = RESTRICTION_TYPES.CUSTOM_URL;
712
713 debug('Restricting access to server due to custom url');
714 }
715 }
716
717 return service;
718 });
696 } 719 }
697 720
721 // Helper
698 _initializeServiceRecipeInWebview(serviceId) { 722 _initializeServiceRecipeInWebview(serviceId) {
699 const service = this.one(serviceId); 723 const service = this.one(serviceId);
700 724
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index a95a8e1e0..9680c5bcc 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -1,4 +1,9 @@
1import { action, observable, computed } from 'mobx'; 1import {
2 action,
3 observable,
4 computed,
5 reaction,
6} from 'mobx';
2import { theme } from '@meetfranz/theme'; 7import { theme } from '@meetfranz/theme';
3 8
4import Store from './lib/Store'; 9import Store from './lib/Store';
@@ -15,10 +20,18 @@ export default class UIStore extends Store {
15 this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this)); 20 this.actions.ui.toggleServiceUpdatedInfoBar.listen(this._toggleServiceUpdatedInfoBar.bind(this));
16 } 21 }
17 22
23 setup() {
24 reaction(
25 () => this.isDarkThemeActive,
26 () => this._setupThemeInDOM(),
27 { fireImmediately: true },
28 );
29 }
30
18 @computed get showMessageBadgesEvenWhenMuted() { 31 @computed get showMessageBadgesEvenWhenMuted() {
19 const settings = this.stores.settings.all; 32 const settings = this.stores.settings.all;
20 33
21 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.isAppMuted; 34 return (settings.app.isAppMuted && settings.app.showMessageBadgeWhenMuted) || !settings.app.isAppMuted;
22 } 35 }
23 36
24 @computed get isDarkThemeActive() { 37 @computed get isDarkThemeActive() {
@@ -26,7 +39,7 @@ export default class UIStore extends Store {
26 } 39 }
27 40
28 @computed get theme() { 41 @computed get theme() {
29 if (this.isDarkThemeActive) return theme('dark'); 42 if (this.isDarkThemeActive || this.stores.settings.app.darkMode) return theme('dark');
30 return theme('default'); 43 return theme('default');
31 } 44 }
32 45
@@ -47,4 +60,15 @@ export default class UIStore extends Store {
47 } 60 }
48 this.showServicesUpdatedInfoBar = visibility; 61 this.showServicesUpdatedInfoBar = visibility;
49 } 62 }
63
64 // Reactions
65 _setupThemeInDOM() {
66 const body = document.querySelector('body');
67
68 if (!this.isDarkThemeActive) {
69 body.classList.remove('theme__dark');
70 } else {
71 body.classList.add('theme__dark');
72 }
73 }
50} 74}
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index b5423af3b..f3dfbdbf0 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -2,12 +2,14 @@ import { observable, computed, action } from 'mobx';
2import moment from 'moment'; 2import moment from 'moment';
3import jwt from 'jsonwebtoken'; 3import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import ms from 'ms';
5 6
6import { isDevMode } from '../environment'; 7import { isDevMode } from '../environment';
7import Store from './lib/Store'; 8import Store from './lib/Store';
8import Request from './lib/Request'; 9import Request from './lib/Request';
9import CachedRequest from './lib/CachedRequest'; 10import CachedRequest from './lib/CachedRequest';
10import { gaEvent } from '../lib/analytics'; 11import { gaEvent } from '../lib/analytics';
12import { sleep } from '../helpers/async-helpers';
11 13
12const debug = require('debug')('Franz:UserStore'); 14const debug = require('debug')('Franz:UserStore');
13 15
@@ -37,6 +39,8 @@ export default class UserStore extends Store {
37 39
38 @observable passwordRequest = new Request(this.api.user, 'password'); 40 @observable passwordRequest = new Request(this.api.user, 'password');
39 41
42 @observable activateTrialRequest = new Request(this.api.user, 'activateTrial');
43
40 @observable inviteRequest = new Request(this.api.user, 'invite'); 44 @observable inviteRequest = new Request(this.api.user, 'invite');
41 45
42 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 46 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
@@ -57,7 +61,9 @@ export default class UserStore extends Store {
57 61
58 @observable accountType; 62 @observable accountType;
59 63
60 @observable hasCompletedSignup = null; 64 @observable hasCompletedSignup = false;
65
66 @observable hasActivatedTrial = false;
61 67
62 @observable userData = {}; 68 @observable userData = {};
63 69
@@ -77,6 +83,7 @@ export default class UserStore extends Store {
77 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); 83 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
78 this.actions.user.logout.listen(this._logout.bind(this)); 84 this.actions.user.logout.listen(this._logout.bind(this));
79 this.actions.user.signup.listen(this._signup.bind(this)); 85 this.actions.user.signup.listen(this._signup.bind(this));
86 this.actions.user.activateTrial.listen(this._activateTrial.bind(this));
80 this.actions.user.invite.listen(this._invite.bind(this)); 87 this.actions.user.invite.listen(this._invite.bind(this));
81 this.actions.user.update.listen(this._update.bind(this)); 88 this.actions.user.update.listen(this._update.bind(this));
82 this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); 89 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
@@ -87,6 +94,7 @@ export default class UserStore extends Store {
87 this.registerReactions([ 94 this.registerReactions([
88 this._requireAuthenticatedUser, 95 this._requireAuthenticatedUser,
89 this._getUserData.bind(this), 96 this._getUserData.bind(this),
97 this._resetTrialActivationState.bind(this),
90 ]); 98 ]);
91 } 99 }
92 100
@@ -199,6 +207,24 @@ export default class UserStore extends Store {
199 gaEvent('User', 'retrievePassword'); 207 gaEvent('User', 'retrievePassword');
200 } 208 }
201 209
210 @action async _activateTrial({ planId }) {
211 debug('activate trial', planId);
212
213 this.activateTrialRequest.execute({
214 plan: planId,
215 });
216
217 await this.activateTrialRequest._promise;
218
219 this.hasActivatedTrial = true;
220
221 this.stores.features.featuresRequest.invalidate({ immediately: true });
222 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
223
224
225 gaEvent('User', 'activateTrial');
226 }
227
202 @action async _invite({ invites }) { 228 @action async _invite({ invites }) {
203 const data = invites.filter(invite => invite.email !== ''); 229 const data = invites.filter(invite => invite.email !== '');
204 230
@@ -318,6 +344,14 @@ export default class UserStore extends Store {
318 } 344 }
319 } 345 }
320 346
347 async _resetTrialActivationState() {
348 if (this.hasActivatedTrial) {
349 await sleep(ms('12s'));
350
351 this.hasActivatedTrial = false;
352 }
353 }
354
321 // Helpers 355 // Helpers
322 _parseToken(authToken) { 356 _parseToken(authToken) {
323 try { 357 try {
@@ -347,6 +381,15 @@ export default class UserStore extends Store {
347 } 381 }
348 } 382 }
349 383
384 getAuthURL(url) {
385 const parsedUrl = new URL(url);
386 const params = new URLSearchParams(parsedUrl.search.slice(1));
387
388 params.append('authToken', this.authToken);
389
390 return `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`;
391 }
392
350 async _migrateUserLocale() { 393 async _migrateUserLocale() {
351 await this.getUserInfoRequest._promise; 394 await this.getUserInfoRequest._promise;
352 395
diff --git a/src/stores/index.js b/src/stores/index.js
index 1912418a2..10dd56665 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -12,6 +12,9 @@ import RequestStore from './RequestStore';
12import GlobalErrorStore from './GlobalErrorStore'; 12import GlobalErrorStore from './GlobalErrorStore';
13import { workspaceStore } from '../features/workspaces'; 13import { workspaceStore } from '../features/workspaces';
14import { announcementsStore } from '../features/announcements'; 14import { announcementsStore } from '../features/announcements';
15import { serviceLimitStore } from '../features/serviceLimit';
16import { communityRecipesStore } from '../features/communityRecipes';
17import { todosStore } from '../features/todos';
15 18
16export default (api, actions, router) => { 19export default (api, actions, router) => {
17 const stores = {}; 20 const stores = {};
@@ -31,6 +34,9 @@ export default (api, actions, router) => {
31 globalError: new GlobalErrorStore(stores, api, actions), 34 globalError: new GlobalErrorStore(stores, api, actions),
32 workspaces: workspaceStore, 35 workspaces: workspaceStore,
33 announcements: announcementsStore, 36 announcements: announcementsStore,
37 serviceLimit: serviceLimitStore,
38 communityRecipes: communityRecipesStore,
39 todos: todosStore,
34 }); 40 });
35 // Initialize all stores 41 // Initialize all stores
36 Object.keys(stores).forEach((name) => { 42 Object.keys(stores).forEach((name) => {
diff --git a/src/stores/lib/Reaction.js b/src/stores/lib/Reaction.js
index f2642908f..f8009b7f6 100644
--- a/src/stores/lib/Reaction.js
+++ b/src/stores/lib/Reaction.js
@@ -13,15 +13,15 @@ export default class Reaction {
13 13
14 start() { 14 start() {
15 if (!this.isRunning) { 15 if (!this.isRunning) {
16 this.dispose = autorun(() => this.reaction()); 16 this.dispose = autorun(this.reaction);
17 this.isActive = true; 17 this.isRunning = true;
18 } 18 }
19 } 19 }
20 20
21 stop() { 21 stop() {
22 if (this.isRunning) { 22 if (this.isRunning) {
23 this.dispose(); 23 this.dispose();
24 this.isActive = false; 24 this.isRunning = false;
25 } 25 }
26 } 26 }
27} 27}
diff --git a/src/styles/auth.scss b/src/styles/auth.scss
index 0a075036a..154a71a36 100644
--- a/src/styles/auth.scss
+++ b/src/styles/auth.scss
@@ -9,7 +9,7 @@
9 } 9 }
10 10
11 .auth__logo.auth__logo--sm { 11 .auth__logo.auth__logo--sm {
12 border: 4px solid $dark-theme-black; 12 border: none;
13 box-shadow: 0 0 6px rgba($dark-theme-black, .5); 13 box-shadow: 0 0 6px rgba($dark-theme-black, .5);
14 } 14 }
15 15
diff --git a/src/styles/layout.scss b/src/styles/layout.scss
index e858b7904..10027da60 100644
--- a/src/styles/layout.scss
+++ b/src/styles/layout.scss
@@ -33,13 +33,19 @@ html { overflow: hidden; }
33} 33}
34 34
35.app { 35.app {
36 display: flex; 36 //display: flex;
37 flex-direction: column; 37
38 .app__content {
39 display: flex;
40 width: calc(100% + 300px);
41 }
38 42
39 .app__content { display: flex; } 43 .app__main-content {
44 display: flex;
45 width: 100%;
46 }
40 47
41 .app__service { 48 .app__service {
42 position: relative;
43 display: flex; 49 display: flex;
44 flex: 1; 50 flex: 1;
45 flex-direction: column; 51 flex-direction: column;
diff --git a/src/styles/recipes.scss b/src/styles/recipes.scss
index 84222e1fe..5bdc60a57 100644
--- a/src/styles/recipes.scss
+++ b/src/styles/recipes.scss
@@ -2,6 +2,7 @@
2 2
3.theme__dark .recipe-teaser { 3.theme__dark .recipe-teaser {
4 background-color: $dark-theme-gray-dark; 4 background-color: $dark-theme-gray-dark;
5 color: $dark-theme-text-color;
5 6
6 &:hover { background-color: $dark-theme-gray; } 7 &:hover { background-color: $dark-theme-gray; }
7} 8}
@@ -12,7 +13,7 @@
12 display: flex; 13 display: flex;
13 flex-flow: row wrap; 14 flex-flow: row wrap;
14 height: auto; 15 height: auto;
15 min-height: 70%; 16 // min-height: 70%;
16 17
17 &.recipes__list--disabled { 18 &.recipes__list--disabled {
18 filter: grayscale(100%); 19 filter: grayscale(100%);
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
index 80328dcef..d87ce652a 100644
--- a/src/styles/reset.scss
+++ b/src/styles/reset.scss
@@ -51,7 +51,6 @@ button {
51 padding: 0; 51 padding: 0;
52 52
53 &:focus { outline: 0; } 53 &:focus { outline: 0; }
54 .theme__dark & { color: $dark-theme-gray-smoke; }
55} 54}
56 55
57html { 56html {
@@ -64,7 +63,7 @@ body {
64 font-size: 1.4rem; 63 font-size: 1.4rem;
65 line-height: 1; 64 line-height: 1;
66 65
67 .theme__dark { color: $dark-theme-gray-smoke; } 66 &.theme__dark { color: $dark-theme-gray-smoke; }
68} 67}
69 68
70* { 69* {
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 1baff8b54..0955aaa0c 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -360,6 +360,7 @@
360 .account__subscription-button { margin-left: auto; } 360 .account__subscription-button { margin-left: auto; }
361 .franz-form__button { white-space: nowrap; } 361 .franz-form__button { white-space: nowrap; }
362 div { height: auto; } 362 div { height: auto; }
363 [data-type="franz-button"] div { height: 100% }
363 364
364 .invoices { 365 .invoices {
365 width: 100%; 366 width: 100%;
diff --git a/src/webview/contextMenu.js b/src/webview/contextMenu.js
index 967e8e667..663b1ebca 100644
--- a/src/webview/contextMenu.js
+++ b/src/webview/contextMenu.js
@@ -39,6 +39,23 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
39 { 39 {
40 type: 'separator', 40 type: 'separator',
41 }, { 41 }, {
42 id: 'createTodo',
43 label: `Create todo: "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
44 visible: hasText,
45 click() {
46 debug('Create todo from selected text', textSelection);
47 ipcRenderer.sendToHost('feature:todos', {
48 action: 'create:todo',
49 data: {
50 title: textSelection,
51 url: window.location.href,
52 },
53 });
54 },
55 },
56 {
57 type: 'separator',
58 }, {
42 id: 'lookup', 59 id: 'lookup',
43 label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`, 60 label: `Look Up "${textSelection.length > 15 ? `${textSelection.slice(0, 15)}...` : textSelection}"`,
44 visible: isMac && props.mediaType === 'none' && hasText, 61 visible: isMac && props.mediaType === 'none' && hasText,
@@ -280,13 +297,12 @@ const buildMenuTpl = (props, suggestions, isSpellcheckEnabled, defaultSpellcheck
280}; 297};
281 298
282export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) { 299export default function contextMenu(spellcheckProvider, isSpellcheckEnabled, getDefaultSpellcheckerLanguage, getSpellcheckerLanguage) {
283 webContents.on('context-menu', async (e, props) => { 300 webContents.on('context-menu', (e, props) => {
284 e.preventDefault(); 301 e.preventDefault();
285 302
286 let suggestions = []; 303 let suggestions = [];
287 if (spellcheckProvider && props.misspelledWord) { 304 if (spellcheckProvider && props.misspelledWord) {
288 debug('Mispelled word', props.misspelledWord); 305 suggestions = spellcheckProvider.getSuggestion(props.misspelledWord);
289 suggestions = await spellcheckProvider.getSuggestion(props.misspelledWord);
290 306
291 debug('Suggestions', suggestions); 307 debug('Suggestions', suggestions);
292 } 308 }
diff --git a/src/webview/spellchecker.js b/src/webview/spellchecker.js
index 417d1ea1a..9158b3b94 100644
--- a/src/webview/spellchecker.js
+++ b/src/webview/spellchecker.js
@@ -1,8 +1,6 @@
1import { webFrame } from 'electron'; 1import { webFrame } from 'electron';
2import { attachSpellCheckProvider, SpellCheckerProvider } from 'electron-hunspell'; 2import { SpellCheckerProvider } from 'electron-hunspell';
3import { ENVIRONMENT } from 'hunspell-asm';
4import path from 'path'; 3import path from 'path';
5import { readFileSync } from 'fs';
6 4
7import { DICTIONARY_PATH } from '../config'; 5import { DICTIONARY_PATH } from '../config';
8import { SPELLCHECKER_LOCALES } from '../i18n/languages'; 6import { SPELLCHECKER_LOCALES } from '../i18n/languages';
@@ -12,12 +10,11 @@ const debug = require('debug')('Franz:spellchecker');
12let provider; 10let provider;
13let currentDict; 11let currentDict;
14let _isEnabled = false; 12let _isEnabled = false;
15let attached;
16 13
17async function loadDictionary(locale) { 14async function loadDictionary(locale) {
18 try { 15 try {
19 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`); 16 const fileLocation = path.join(DICTIONARY_PATH, `hunspell-dict-${locale}/${locale}`);
20 await provider.loadDictionary(locale, readFileSync(`${fileLocation}.dic`), readFileSync(`${fileLocation}.aff`)); 17 await provider.loadDictionary(locale, `${fileLocation}.dic`, `${fileLocation}.aff`);
21 debug('Loaded dictionary', locale, 'from', fileLocation); 18 debug('Loaded dictionary', locale, 'from', fileLocation);
22 } catch (err) { 19 } catch (err) {
23 console.error('Could not load dictionary', err); 20 console.error('Could not load dictionary', err);
@@ -44,7 +41,7 @@ export async function switchDict(locale) {
44 provider.unloadDictionary(locale); 41 provider.unloadDictionary(locale);
45 } 42 }
46 loadDictionary(locale); 43 loadDictionary(locale);
47 attached.switchLanguage(locale); 44 provider.switchDictionary(locale);
48 45
49 debug('Switched dictionary to', locale); 46 debug('Switched dictionary to', locale);
50 47
@@ -61,14 +58,12 @@ export default async function initialize(languageCode = 'en-us') {
61 const locale = languageCode.toLowerCase(); 58 const locale = languageCode.toLowerCase();
62 59
63 debug('Init spellchecker'); 60 debug('Init spellchecker');
64 await provider.initialize({ environment: ENVIRONMENT.NODE }); 61 await provider.initialize();
65 62 // await loadDictionaries();
66 debug('Attaching spellcheck provider');
67 attached = await attachSpellCheckProvider(provider);
68 63
69 debug('Available spellchecker dictionaries', provider.availableDictionaries); 64 debug('Available spellchecker dictionaries', provider.availableDictionaries);
70 65
71 attached.switchLanguage(locale); 66 switchDict(locale);
72 67
73 return provider; 68 return provider;
74 } catch (err) { 69 } catch (err) {