aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package-lock.json27
-rw-r--r--package.json2
-rw-r--r--packages/theme/src/themes/dark/index.ts10
-rw-r--r--packages/theme/src/themes/default/index.ts12
-rw-r--r--packages/ui/src/badge/ProBadge.tsx5
-rw-r--r--packages/ui/src/infobox/index.tsx2
-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/Pricing.js246
-rw-r--r--src/components/auth/Signup.js30
-rw-r--r--src/components/layout/AppLayout.js27
-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.js32
-rw-r--r--src/containers/auth/PricingScreen.js40
-rw-r--r--src/containers/layout/AppLayoutContainer.js6
-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/TeamScreen.js1
-rw-r--r--src/containers/subscription/SubscriptionFormScreen.js99
-rw-r--r--src/containers/subscription/SubscriptionPopupScreen.js3
-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.js2
-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/workspaces/store.js6
-rw-r--r--src/helpers/plan-helpers.js35
-rw-r--r--src/i18n/locales/defaultMessages.json1138
-rw-r--r--src/i18n/locales/en-US.json79
-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.json13
-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/index.js29
-rw-r--r--src/models/Service.js15
-rw-r--r--src/models/User.js14
-rw-r--r--src/stores/AppStore.js2
-rw-r--r--src/stores/FeaturesStore.js4
-rw-r--r--src/stores/ServicesStore.js39
-rw-r--r--src/stores/UserStore.js47
-rw-r--r--src/stores/index.js4
-rw-r--r--src/styles/auth.scss2
-rw-r--r--src/styles/recipes.scss3
-rw-r--r--src/styles/reset.scss1
-rw-r--r--src/styles/settings.scss1
86 files changed, 3455 insertions, 1246 deletions
diff --git a/package-lock.json b/package-lock.json
index 607ebc31a..048425eb6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2149,9 +2149,9 @@
2149 } 2149 }
2150 }, 2150 },
2151 "@mdi/js": { 2151 "@mdi/js": {
2152 "version": "3.4.93", 2152 "version": "4.2.95",
2153 "resolved": "https://registry.npmjs.org/@mdi/js/-/js-3.4.93.tgz", 2153 "resolved": "https://registry.npmjs.org/@mdi/js/-/js-4.2.95.tgz",
2154 "integrity": "sha512-SEzolEqT8ErlWdHz4AAtQ1lTfAnM6j67Ppm6k5s/I1aIuuoFP/D8d/z838C28xHO1KOqrsS1fw2wlf6fRiEEJA==" 2154 "integrity": "sha512-3qqOZx2HkrQEUc9fr5MiQWlokwmO8TK5bQZ2EP1Rg0q2Q507jy+fUeL8lb9ko2ossYqoPnugIr7jI0/O7uhlrA=="
2155 }, 2155 },
2156 "@mdi/react": { 2156 "@mdi/react": {
2157 "version": "1.1.0", 2157 "version": "1.1.0",
@@ -2178,6 +2178,10 @@
2178 "react-loader": "^2.4.5" 2178 "react-loader": "^2.4.5"
2179 }, 2179 },
2180 "dependencies": { 2180 "dependencies": {
2181 "@mdi/js": {
2182 "version": "3.9.97",
2183 "bundled": true
2184 },
2181 "@meetfranz/theme": { 2185 "@meetfranz/theme": {
2182 "version": "1.0.14", 2186 "version": "1.0.14",
2183 "bundled": true, 2187 "bundled": true,
@@ -2202,6 +2206,10 @@
2202 "react-loader": "^2.4.5" 2206 "react-loader": "^2.4.5"
2203 }, 2207 },
2204 "dependencies": { 2208 "dependencies": {
2209 "@mdi/js": {
2210 "version": "3.9.97",
2211 "bundled": true
2212 },
2205 "@meetfranz/theme": { 2213 "@meetfranz/theme": {
2206 "version": "1.0.14", 2214 "version": "1.0.14",
2207 "bundled": true, 2215 "bundled": true,
@@ -16748,6 +16756,14 @@
16748 "react-transition-group": "^1.2.0" 16756 "react-transition-group": "^1.2.0"
16749 } 16757 }
16750 }, 16758 },
16759 "react-confetti": {
16760 "version": "3.1.0",
16761 "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-3.1.0.tgz",
16762 "integrity": "sha512-T1DKt09C9rrf2BJ0OxFu8saPohFalOJfOgci11/ePz6DxbY+0cd/3CMimxj/ZITl7jQWnmC/bNWBgGheke3WZQ==",
16763 "requires": {
16764 "tween-functions": "^1.2.0"
16765 }
16766 },
16751 "react-dom": { 16767 "react-dom": {
16752 "version": "16.6.3", 16768 "version": "16.6.3",
16753 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", 16769 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
@@ -19899,6 +19915,11 @@
19899 "safe-buffer": "^5.0.1" 19915 "safe-buffer": "^5.0.1"
19900 } 19916 }
19901 }, 19917 },
19918 "tween-functions": {
19919 "version": "1.2.0",
19920 "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
19921 "integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
19922 },
19902 "tweetnacl": { 19923 "tweetnacl": {
19903 "version": "0.14.5", 19924 "version": "0.14.5",
19904 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 19925 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
diff --git a/package.json b/package.json
index 639d078a0..d30fa08c5 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
36 "dependencies": { 36 "dependencies": {
37 "@babel/polyfill": "7.4.4", 37 "@babel/polyfill": "7.4.4",
38 "@babel/runtime": "7.4.5", 38 "@babel/runtime": "7.4.5",
39 "@mdi/js": "4.2.95",
39 "@meetfranz/electron-notification-state": "1.0.0", 40 "@meetfranz/electron-notification-state": "1.0.0",
40 "@meetfranz/forms": "file:packages/forms", 41 "@meetfranz/forms": "file:packages/forms",
41 "@meetfranz/theme": "file:packages/theme", 42 "@meetfranz/theme": "file:packages/theme",
@@ -71,6 +72,7 @@
71 "prop-types": "^15.5.10", 72 "prop-types": "^15.5.10",
72 "react": "16.6.3", 73 "react": "16.6.3",
73 "react-addons-css-transition-group": "15.6.2", 74 "react-addons-css-transition-group": "15.6.2",
75 "react-confetti": "3.1.0",
74 "react-dom": "16.6.3", 76 "react-dom": "16.6.3",
75 "react-dropzone": "7.0.1", 77 "react-dropzone": "7.0.1",
76 "react-electron-web-view": "^2.0.1", 78 "react-electron-web-view": "^2.0.1",
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index d29345298..93c18efde 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -119,6 +119,16 @@ export const announcements = merge({}, defaultStyles.announcements, {
119 }, 119 },
120}); 120});
121 121
122// Signup
123export const signup = merge({}, defaultStyles.signup, {
124 pricing: {
125 feature: {
126 background: legacyStyles.darkThemeGrayLight,
127 border: color(legacyStyles.darkThemeGrayLight).lighten(0.2).hex(),
128 },
129 },
130});
131
122// Todos 132// Todos
123export const todos = merge({}, defaultStyles.todos, { 133export const todos = merge({}, defaultStyles.todos, {
124 todosLayer: { 134 todosLayer: {
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 9f9c39f9a..7edf8331f 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -208,6 +208,16 @@ export const announcements = {
208 }, 208 },
209}; 209};
210 210
211// Signup
212export const signup = {
213 pricing: {
214 feature: {
215 background: legacyStyles.themeGrayLightest,
216 border: legacyStyles.themeGrayLighter,
217 },
218 },
219};
220
211// Todos 221// Todos
212export const todos = { 222export const todos = {
213 todosLayer: { 223 todosLayer: {
@@ -223,5 +233,5 @@ export const todos = {
223 }, 233 },
224 resizeHandler: { 234 resizeHandler: {
225 backgroundHover: styleTypes.primary.accent, 235 backgroundHover: styleTypes.primary.accent,
226 } 236 },
227}; 237};
diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx
index c18cb4a0c..5cc41f5b2 100644
--- a/packages/ui/src/badge/ProBadge.tsx
+++ b/packages/ui/src/badge/ProBadge.tsx
@@ -4,13 +4,14 @@ import classnames from 'classnames';
4import React, { Component } from 'react'; 4import React, { Component } from 'react';
5import injectStyle from 'react-jss'; 5import injectStyle from 'react-jss';
6 6
7import { Icon, Badge } from '../'; 7import { Badge, Icon } from '../';
8import { IWithStyle } from '../typings/generic'; 8import { IWithStyle } from '../typings/generic';
9 9
10interface IProps extends IWithStyle { 10interface IProps extends IWithStyle {
11 badgeClasses?: string; 11 badgeClasses?: string;
12 iconClasses?: string; 12 iconClasses?: string;
13 inverted?: boolean; 13 inverted?: boolean;
14 className?: string;
14} 15}
15 16
16const styles = (theme: Theme) => ({ 17const styles = (theme: Theme) => ({
@@ -38,6 +39,7 @@ class ProBadgeComponent extends Component<IProps> {
38 badgeClasses, 39 badgeClasses,
39 iconClasses, 40 iconClasses,
40 inverted, 41 inverted,
42 className,
41 } = this.props; 43 } = this.props;
42 44
43 return ( 45 return (
@@ -47,6 +49,7 @@ class ProBadgeComponent extends Component<IProps> {
47 classes.badge, 49 classes.badge,
48 inverted && classes.invertedBadge, 50 inverted && classes.invertedBadge,
49 badgeClasses, 51 badgeClasses,
52 className,
50 ])} 53 ])}
51 > 54 >
52 <Icon 55 <Icon
diff --git a/packages/ui/src/infobox/index.tsx b/packages/ui/src/infobox/index.tsx
index 5070ee7ef..e4c2c5a3e 100644
--- a/packages/ui/src/infobox/index.tsx
+++ b/packages/ui/src/infobox/index.tsx
@@ -49,13 +49,13 @@ const styles = (theme: Theme) => ({
49 position: 'relative', 49 position: 'relative',
50 overflow: 'hidden', 50 overflow: 'hidden',
51 height: 'auto', 51 height: 'auto',
52 marginBottom: 30,
52 }, 53 },
53 infobox: { 54 infobox: {
54 alignItems: 'center', 55 alignItems: 'center',
55 borderRadius: theme.borderRadiusSmall, 56 borderRadius: theme.borderRadiusSmall,
56 display: 'flex', 57 display: 'flex',
57 height: 'auto', 58 height: 'auto',
58 marginBottom: 30,
59 padding: '15px 20px', 59 padding: '15px 20px',
60 top: 0, 60 top: 0,
61 transition: 'all 0.5s', 61 transition: 'all 0.5s',
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/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..7724bb908 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -31,10 +31,10 @@ const messages = defineMessages({
31 id: 'signup.email.label', 31 id: 'signup.email.label',
32 defaultMessage: '!!!Email address', 32 defaultMessage: '!!!Email address',
33 }, 33 },
34 companyLabel: { 34 // companyLabel: {
35 id: 'signup.company.label', 35 // id: 'signup.company.label',
36 defaultMessage: '!!!Company', 36 // defaultMessage: '!!!Company',
37 }, 37 // },
38 passwordLabel: { 38 passwordLabel: {
39 id: 'signup.password.label', 39 id: 'signup.password.label',
40 defaultMessage: '!!!Password', 40 defaultMessage: '!!!Password',
@@ -79,20 +79,6 @@ export default @observer class Signup extends Component {
79 79
80 form = new Form({ 80 form = new Form({
81 fields: { 81 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: { 82 firstname: {
97 label: this.context.intl.formatMessage(messages.firstnameLabel), 83 label: this.context.intl.formatMessage(messages.firstnameLabel),
98 value: '', 84 value: '',
@@ -108,10 +94,6 @@ export default @observer class Signup extends Component {
108 value: '', 94 value: '',
109 validators: [required, email], 95 validators: [required, email],
110 }, 96 },
111 organization: {
112 label: this.context.intl.formatMessage(messages.companyLabel),
113 value: '', // TODO: make required when accountType: company
114 },
115 password: { 97 password: {
116 label: this.context.intl.formatMessage(messages.passwordLabel), 98 label: this.context.intl.formatMessage(messages.passwordLabel),
117 value: '', 99 value: '',
@@ -151,7 +133,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. 133 In Dev Mode your data is not persistent. Please use the live app for accesing the production API.
152 </Infobox> 134 </Infobox>
153 )} 135 )}
154 <Radio field={form.$('accountType')} showLabel={false} />
155 <div className="grid__row"> 136 <div className="grid__row">
156 <Input field={form.$('firstname')} focus /> 137 <Input field={form.$('firstname')} focus />
157 <Input field={form.$('lastname')} /> 138 <Input field={form.$('lastname')} />
@@ -162,9 +143,6 @@ export default @observer class Signup extends Component {
162 showPasswordToggle 143 showPasswordToggle
163 scorePassword 144 scorePassword
164 /> 145 />
165 {form.$('accountType').value === 'company' && (
166 <Input field={form.$('organization')} />
167 )}
168 {error.code === 'email-duplicate' && ( 146 {error.code === 'email-duplicate' && (
169 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p> 147 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p>
170 )} 148 )}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index dbf7d3c21..941e60bfd 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -17,6 +17,7 @@ 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';
20import Todos from '../../features/todos/containers/TodosScreen'; 21import Todos from '../../features/todos/containers/TodosScreen';
21 22
22function createMarkup(HTMLString) { 23function createMarkup(HTMLString) {
@@ -70,6 +71,7 @@ class AppLayout extends Component {
70 retryRequiredRequests: PropTypes.func.isRequired, 71 retryRequiredRequests: PropTypes.func.isRequired,
71 areRequiredRequestsLoading: PropTypes.bool.isRequired, 72 areRequiredRequestsLoading: 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,
@@ -102,6 +103,7 @@ class AppLayout extends Component {
102 retryRequiredRequests, 103 retryRequiredRequests,
103 areRequiredRequestsLoading, 104 areRequiredRequestsLoading,
104 isDelayAppScreenVisible, 105 isDelayAppScreenVisible,
106 hasActivatedTrial,
105 } = this.props; 107 } = this.props;
106 108
107 const { intl } = this.context; 109 const { intl } = this.context;
@@ -126,17 +128,20 @@ class AppLayout extends Component {
126 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 128 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
127 </InfoBar> 129 </InfoBar>
128 ))} 130 ))}
131 {hasActivatedTrial && (
132 <TrialActivationInfoBar />
133 )}
129 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 134 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
130 <InfoBar 135 <InfoBar
131 type="danger" 136 type="danger"
132 ctaLabel="Try again" 137 ctaLabel="Try again"
133 ctaLoading={areRequiredRequestsLoading} 138 ctaLoading={areRequiredRequestsLoading}
134 sticky 139 sticky
135 onClick={retryRequiredRequests} 140 onClick={retryRequiredRequests}
136 > 141 >
137 <span className="mdi mdi-flash" /> 142 <span className="mdi mdi-flash" />
138 {intl.formatMessage(messages.requiredRequestsFailed)} 143 {intl.formatMessage(messages.requiredRequestsFailed)}
139 </InfoBar> 144 </InfoBar>
140 )} 145 )}
141 {showServicesUpdatedInfoBar && ( 146 {showServicesUpdatedInfoBar && (
142 <InfoBar 147 <InfoBar
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 471f8d5a6..405cc5253 100644
--- a/src/config.js
+++ b/src/config.js
@@ -19,7 +19,8 @@ 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';
@@ -49,16 +50,16 @@ export const DEFAULT_APP_SETTINGS = {
49}; 50};
50 51
51export const DEFAULT_FEATURES_CONFIG = { 52export const DEFAULT_FEATURES_CONFIG = {
52 isSpellcheckerPremiumFeature: false, 53 isSpellcheckerIncludedInCurrentPlan: true,
53 needToWaitToProceed: false, 54 needToWaitToProceed: false,
54 needToWaitToProceedConfig: { 55 needToWaitToProceedConfig: {
55 delayOffset: ms('1h'), 56 delayOffset: ms('1h'),
56 wait: ms('10s'), 57 wait: ms('10s'),
57 }, 58 },
58 isServiceProxyEnabled: false, 59 isServiceProxyEnabled: false,
59 isServiceProxyPremiumFeature: true, 60 isServiceProxyIncludedInCurrentPlan: false,
60 isAnnouncementsEnabled: true, 61 isAnnouncementsEnabled: true,
61 isWorkspacePremiumFeature: true, 62 isWorkspaceIncludedInCurrentPlan: true,
62 isWorkspaceEnabled: false, 63 isWorkspaceEnabled: false,
63}; 64};
64 65
@@ -71,6 +72,7 @@ export const DEFAULT_WINDOW_OPTIONS = {
71 72
72export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs'; 73export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs';
73export 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';
74 76
75export const FILE_SYSTEM_SETTINGS_TYPES = [ 77export const FILE_SYSTEM_SETTINGS_TYPES = [
76 'app', 78 'app',
@@ -87,3 +89,25 @@ export const ALLOWED_PROTOCOLS = [
87 'http:', 89 'http:',
88 'ftp:', 90 'ftp:',
89]; 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/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 cf3da71e8..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
@@ -149,6 +153,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
149 retryRequiredRequests={retryRequiredRequests} 153 retryRequiredRequests={retryRequiredRequests}
150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 154 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
151 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} 155 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible}
156 hasActivatedTrial={user.hasActivatedTrial}
152 > 157 >
153 {React.Children.count(children) > 0 ? children : null} 158 {React.Children.count(children) > 0 ? children : null}
154 </AppLayout> 159 </AppLayout>
@@ -166,6 +171,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
166 ui: PropTypes.instanceOf(UIStore).isRequired, 171 ui: PropTypes.instanceOf(UIStore).isRequired,
167 news: PropTypes.instanceOf(NewsStore).isRequired, 172 news: PropTypes.instanceOf(NewsStore).isRequired,
168 settings: PropTypes.instanceOf(SettingsStore).isRequired, 173 settings: PropTypes.instanceOf(SettingsStore).isRequired,
174 user: PropTypes.instanceOf(UserStore).isRequired,
169 requests: PropTypes.instanceOf(RequestStore).isRequired, 175 requests: PropTypes.instanceOf(RequestStore).isRequired,
170 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, 176 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
171 }).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/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/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 39fae3b20..627537de7 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -44,7 +44,7 @@ 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 debug('seas', stores.services.all.length);
49 shownAfterLaunch = true; 49 shownAfterLaunch = true;
50 return; 50 return;
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/workspaces/store.js b/src/features/workspaces/store.js
index a82f6895c..e44569be9 100644
--- a/src/features/workspaces/store.js
+++ b/src/features/workspaces/store.js
@@ -255,9 +255,9 @@ export default class WorkspacesStore extends FeatureStore {
255 _setIsPremiumFeatureReaction = () => { 255 _setIsPremiumFeatureReaction = () => {
256 const { features, user } = this.stores; 256 const { features, user } = this.stores;
257 const { isPremium } = user.data; 257 const { isPremium } = user.data;
258 const { isWorkspacePremiumFeature } = features.features; 258 const { isWorkspaceIncludedInCurrentPlan } = features.features;
259 this.isPremiumFeature = isWorkspacePremiumFeature; 259 this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan;
260 this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium; 260 this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan && !isPremium;
261 }; 261 };
262 262
263 _setWorkspaceBeingEditedReaction = () => { 263 _setWorkspaceBeingEditedReaction = () => {
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 5959fb059..2f134c5ee 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
463 },
464 "file": "src/components/auth/Pricing.js",
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
450 }, 476 },
451 "file": "src/components/auth/Pricing.js", 477 "file": "src/components/auth/Pricing.js",
452 "id": "pricing.submit.label", 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 ],
@@ -526,19 +591,6 @@
526 } 591 }
527 }, 592 },
528 { 593 {
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 }
540 },
541 {
542 "defaultMessage": "!!!Password", 594 "defaultMessage": "!!!Password",
543 "end": { 595 "end": {
544 "column": 3, 596 "column": 3,
@@ -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",
2697 "end": {
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",
2445 "end": { 2710 "end": {
2446 "column": 3, 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
2448 }, 2726 },
2449 "file": "src/components/subscription/SubscriptionForm.js", 2727 "file": "src/components/subscription/SubscriptionForm.js",
2450 "id": "subscription.submit.label", 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
2739 },
2740 "file": "src/components/subscription/SubscriptionForm.js",
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,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index cee42c350..e8680f2d9 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",
@@ -110,10 +114,33 @@
110 "password.submit.label": "Submit", 114 "password.submit.label": "Submit",
111 "password.successInfo": "Please check your email", 115 "password.successInfo": "Please check your email",
112 "premiumFeature.button.upgradeAccount": "Upgrade account", 116 "premiumFeature.button.upgradeAccount": "Upgrade account",
113 "pricing.headline": "Support Franz", 117 "pricing.features.adFree": "Forever ad-free",
114 "pricing.link.skipPayment": "I don't want to support the development of Franz.", 118 "pricing.features.appDelays": "No Waiting Screens",
115 "pricing.submit.label": "I want to support the development of Franz", 119 "pricing.features.customWebsites": "Add Custom Websites",
116 "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",
117 "service.crashHandler.action": "Reload {name}", 144 "service.crashHandler.action": "Reload {name}",
118 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", 145 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds",
119 "service.crashHandler.headline": "Oh no!", 146 "service.crashHandler.headline": "Oh no!",
@@ -125,6 +152,11 @@
125 "service.errorHandler.headline": "Oh no!", 152 "service.errorHandler.headline": "Oh no!",
126 "service.errorHandler.message": "Error", 153 "service.errorHandler.message": "Error",
127 "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.",
128 "service.webviewLoader.loading": "Loading", 160 "service.webviewLoader.loading": "Loading",
129 "services.getStarted": "Get started", 161 "services.getStarted": "Get started",
130 "services.welcome": "Welcome to Franz", 162 "services.welcome": "Welcome to Franz",
@@ -142,13 +174,19 @@
142 "settings.account.headlinePassword": "Change password", 174 "settings.account.headlinePassword": "Change password",
143 "settings.account.headlineProfile": "Update profile", 175 "settings.account.headlineProfile": "Update profile",
144 "settings.account.headlineSubscription": "Your subscription", 176 "settings.account.headlineSubscription": "Your subscription",
145 "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",
146 "settings.account.invoiceDownload": "Download", 179 "settings.account.invoiceDownload": "Download",
147 "settings.account.manageSubscription.label": "Manage your subscription", 180 "settings.account.manageSubscription.label": "Manage your subscription",
148 "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.",
149 "settings.account.tryReloadServices": "Try again", 185 "settings.account.tryReloadServices": "Try again",
150 "settings.account.tryReloadUserInfoRequest": "Try again", 186 "settings.account.tryReloadUserInfoRequest": "Try again",
187 "settings.account.upgradeToPro.label": "Upgrade to Franz Professional",
151 "settings.account.userInfoRequestFailed": "Could not load user information", 188 "settings.account.userInfoRequestFailed": "Could not load user information",
189 "settings.account.yourLicense": "Your Franz License",
152 "settings.app.buttonClearAllCache": "Clear cache", 190 "settings.app.buttonClearAllCache": "Clear cache",
153 "settings.app.buttonInstallUpdate": "Restart & install update", 191 "settings.app.buttonInstallUpdate": "Restart & install update",
154 "settings.app.buttonSearchForUpdate": "Check for updates", 192 "settings.app.buttonSearchForUpdate": "Check for updates",
@@ -189,7 +227,13 @@
189 "settings.navigation.yourServices": "Your services", 227 "settings.navigation.yourServices": "Your services",
190 "settings.navigation.yourWorkspaces": "Your workspaces", 228 "settings.navigation.yourWorkspaces": "Your workspaces",
191 "settings.recipes.all": "All services", 229 "settings.recipes.all": "All services",
192 "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",
193 "settings.recipes.headline": "Available services", 237 "settings.recipes.headline": "Available services",
194 "settings.recipes.missingService": "Missing a service?", 238 "settings.recipes.missingService": "Missing a service?",
195 "settings.recipes.mostPopular": "Most popular", 239 "settings.recipes.mostPopular": "Most popular",
@@ -281,7 +325,6 @@
281 "sidebar.openWorkspaceDrawer": "Open workspace drawer", 325 "sidebar.openWorkspaceDrawer": "Open workspace drawer",
282 "sidebar.settings": "Settings", 326 "sidebar.settings": "Settings",
283 "sidebar.unmuteApp": "Enable notifications & audio", 327 "sidebar.unmuteApp": "Enable notifications & audio",
284 "signup.company.label": "Company",
285 "signup.email.label": "Email address", 328 "signup.email.label": "Email address",
286 "signup.emailDuplicate": "A user with that email address already exists", 329 "signup.emailDuplicate": "A user with that email address already exists",
287 "signup.firstname.label": "First Name", 330 "signup.firstname.label": "First Name",
@@ -293,20 +336,12 @@
293 "signup.link.login": "Already have an account, sign in?", 336 "signup.link.login": "Already have an account, sign in?",
294 "signup.password.label": "Password", 337 "signup.password.label": "Password",
295 "signup.submit.label": "Create account", 338 "signup.submit.label": "Create account",
296 "subscription.euTaxInfo": "EU residents: local sales tax may apply", 339 "subscription.cta.activateTrial": "Yes, start the free Franz Professional trial",
297 "subscription.features.ads": "No ads, ever!", 340 "subscription.cta.allOptions": "See all options",
298 "subscription.features.comingSoon": "coming soon", 341 "subscription.cta.choosePlan": "Choose your plan",
299 "subscription.features.noInterruptions": "No app delays & nagging to upgrade license", 342 "subscription.includedProFeatures": "The Franz Professional Plan includes:",
300 "subscription.features.onpremise.mattermost": "Add on-premise/hosted services like Mattermost", 343 "subscription.teaser.includedFeatures": "Paid Franz Plans include:",
301 "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!",
302 "subscription.features.spellchecker": "Support for spellchecker",
303 "subscription.features.workspaces": "Organize your services in workspaces",
304 "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes",
305 "subscription.paymentSessionError": "Could not initialize payment form",
306 "subscription.submit.label": "I want to support the development of Franz",
307 "subscription.type.free": "free",
308 "subscription.type.month": "month",
309 "subscription.type.year": "year",
310 "subscriptionPopup.buttonCancel": "Cancel", 345 "subscriptionPopup.buttonCancel": "Cancel",
311 "subscriptionPopup.buttonDone": "Done", 346 "subscriptionPopup.buttonDone": "Done",
312 "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..f97b936fc 100644
--- a/src/i18n/messages/src/components/auth/Signup.json
+++ b/src/i18n/messages/src/components/auth/Signup.json
@@ -52,19 +52,6 @@
52 } 52 }
53 }, 53 },
54 { 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
65 }
66 },
67 {
68 "id": "signup.password.label", 55 "id": "signup.password.label",
69 "defaultMessage": "!!!Password", 56 "defaultMessage": "!!!Password",
70 "file": "src/components/auth/Signup.js", 57 "file": "src/components/auth/Signup.js",
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/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/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 2ac306a2a..6054e6721 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -357,6 +357,8 @@ export default class AppStore extends Store {
357 this.locale = this._getDefaultLocale(); 357 this.locale = this._getDefaultLocale();
358 } 358 }
359 359
360 moment.locale(this.locale);
361
360 debug(`Set locale to "${this.locale}"`); 362 debug(`Set locale to "${this.locale}"`);
361 } 363 }
362 364
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 35a050c67..cf28b6bec 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -16,6 +16,8 @@ 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';
19import todos from '../features/todos'; 21import todos from '../features/todos';
20 22
21import { DEFAULT_FEATURES_CONFIG } from '../config'; 23import { DEFAULT_FEATURES_CONFIG } from '../config';
@@ -76,6 +78,8 @@ export default class FeaturesStore extends Store {
76 shareFranz(this.stores, this.actions); 78 shareFranz(this.stores, this.actions);
77 announcements(this.stores, this.actions); 79 announcements(this.stores, this.actions);
78 settingsWS(this.stores, this.actions); 80 settingsWS(this.stores, this.actions);
81 serviceLimit(this.stores, this.actions);
82 communityRecipes(this.stores, this.actions);
79 todos(this.stores, this.actions); 83 todos(this.stores, this.actions);
80 } 84 }
81} 85}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index b13425249..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 [];
@@ -153,6 +159,8 @@ export default class ServicesStore extends Store {
153 159
154 // Actions 160 // Actions
155 @action async _createService({ recipeId, serviceData, redirect = true }) { 161 @action async _createService({ recipeId, serviceData, redirect = true }) {
162 if (serviceLimitStore.userHasReachedServiceLimit) return;
163
156 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 164 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
157 165
158 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 166 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
@@ -681,6 +689,35 @@ export default class ServicesStore extends Store {
681 return serviceData; 689 return serviceData;
682 } 690 }
683 691
692 _restrictServiceAccess() {
693 const { features } = this.stores.features;
694 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
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 });
719 }
720
684 // Helper 721 // Helper
685 _initializeServiceRecipeInWebview(serviceId) { 722 _initializeServiceRecipeInWebview(serviceId) {
686 const service = this.one(serviceId); 723 const service = this.one(serviceId);
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index b5423af3b..e23106462 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, WEBSITE } 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 e9ef523f8..10dd56665 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -12,6 +12,8 @@ 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';
15import { todosStore } from '../features/todos'; 17import { todosStore } from '../features/todos';
16 18
17export default (api, actions, router) => { 19export default (api, actions, router) => {
@@ -32,6 +34,8 @@ export default (api, actions, router) => {
32 globalError: new GlobalErrorStore(stores, api, actions), 34 globalError: new GlobalErrorStore(stores, api, actions),
33 workspaces: workspaceStore, 35 workspaces: workspaceStore,
34 announcements: announcementsStore, 36 announcements: announcementsStore,
37 serviceLimit: serviceLimitStore,
38 communityRecipes: communityRecipesStore,
35 todos: todosStore, 39 todos: todosStore,
36 }); 40 });
37 // Initialize all stores 41 // Initialize all stores
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/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 f46ede4a2..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 {
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%;