aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--package-lock.json15
-rw-r--r--package.json1
-rw-r--r--packages/theme/src/themes/dark/index.ts10
-rw-r--r--packages/theme/src/themes/default/index.ts10
-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/layout/AppLayout.js37
-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.js50
-rw-r--r--src/components/settings/account/AccountDashboard.js198
-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/subscription/SubscriptionForm.js238
-rw-r--r--src/components/ui/FeatureItem.js36
-rw-r--r--src/components/ui/FeatureList.js89
-rw-r--r--src/config.js31
-rw-r--r--src/containers/auth/PricingScreen.js40
-rw-r--r--src/containers/layout/AppLayoutContainer.js6
-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/subscription/SubscriptionFormScreen.js18
-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/spellchecker/index.js8
-rw-r--r--src/features/workspaces/store.js6
-rw-r--r--src/helpers/plan-helpers.js43
-rw-r--r--src/i18n/locales/defaultMessages.json983
-rw-r--r--src/i18n/locales/en-US.json52
-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/layout/AppLayout.json12
-rw-r--r--src/i18n/messages/src/components/services/content/ServiceRestricted.json67
-rw-r--r--src/i18n/messages/src/components/services/content/Services.json8
-rw-r--r--src/i18n/messages/src/components/settings/account/AccountDashboard.json116
-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.json145
-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/helpers/plan-helpers.json80
-rw-r--r--src/i18n/messages/src/helpers/pricing-helpers.json80
-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.js40
-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
73 files changed, 3177 insertions, 946 deletions
diff --git a/package-lock.json b/package-lock.json
index b4d86c5e1..13646552b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "franz", 2 "name": "franz",
3 "version": "5.2.0-beta.3", 3 "version": "5.2.0-beta.4",
4 "lockfileVersion": 1, 4 "lockfileVersion": 1,
5 "requires": true, 5 "requires": true,
6 "dependencies": { 6 "dependencies": {
@@ -16748,6 +16748,14 @@
16748 "react-transition-group": "^1.2.0" 16748 "react-transition-group": "^1.2.0"
16749 } 16749 }
16750 }, 16750 },
16751 "react-confetti": {
16752 "version": "3.1.0",
16753 "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-3.1.0.tgz",
16754 "integrity": "sha512-T1DKt09C9rrf2BJ0OxFu8saPohFalOJfOgci11/ePz6DxbY+0cd/3CMimxj/ZITl7jQWnmC/bNWBgGheke3WZQ==",
16755 "requires": {
16756 "tween-functions": "^1.2.0"
16757 }
16758 },
16751 "react-dom": { 16759 "react-dom": {
16752 "version": "16.6.3", 16760 "version": "16.6.3",
16753 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", 16761 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
@@ -19899,6 +19907,11 @@
19899 "safe-buffer": "^5.0.1" 19907 "safe-buffer": "^5.0.1"
19900 } 19908 }
19901 }, 19909 },
19910 "tween-functions": {
19911 "version": "1.2.0",
19912 "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
19913 "integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
19914 },
19902 "tweetnacl": { 19915 "tweetnacl": {
19903 "version": "0.14.5", 19916 "version": "0.14.5",
19904 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 19917 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
diff --git a/package.json b/package.json
index 639d078a0..f7ae953f4 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
71 "prop-types": "^15.5.10", 71 "prop-types": "^15.5.10",
72 "react": "16.6.3", 72 "react": "16.6.3",
73 "react-addons-css-transition-group": "15.6.2", 73 "react-addons-css-transition-group": "15.6.2",
74 "react-confetti": "3.1.0",
74 "react-dom": "16.6.3", 75 "react-dom": "16.6.3",
75 "react-dropzone": "7.0.1", 76 "react-dropzone": "7.0.1",
76 "react-electron-web-view": "^2.0.1", 77 "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 bd9f001e8..1ea46cd1a 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -118,3 +118,13 @@ export const announcements = merge({}, defaultStyles.announcements, {
118 background: legacyStyles.darkThemeGrayDark, 118 background: legacyStyles.darkThemeGrayDark,
119 }, 119 },
120}); 120});
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});
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 0f02fa3c8..0d99bc4be 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -207,3 +207,13 @@ export const announcements = {
207 background: legacyStyles.themeGrayLightest, 207 background: legacyStyles.themeGrayLightest,
208 }, 208 },
209}; 209};
210
211// Signup
212export const signup = {
213 pricing: {
214 feature: {
215 background: legacyStyles.themeGrayLightest,
216 border: legacyStyles.themeGrayLighter,
217 },
218 },
219};
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/layout/AppLayout.js b/src/components/layout/AppLayout.js
index ebb9849ea..1976f5a50 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';
20 21
21function createMarkup(HTMLString) { 22function createMarkup(HTMLString) {
22 return { __html: HTMLString }; 23 return { __html: HTMLString };
@@ -57,7 +58,6 @@ class AppLayout extends Component {
57 services: PropTypes.element.isRequired, 58 services: PropTypes.element.isRequired,
58 children: PropTypes.element, 59 children: PropTypes.element,
59 news: MobxPropTypes.arrayOrObservableArray.isRequired, 60 news: MobxPropTypes.arrayOrObservableArray.isRequired,
60 // isOnline: PropTypes.bool.isRequired,
61 showServicesUpdatedInfoBar: PropTypes.bool.isRequired, 61 showServicesUpdatedInfoBar: PropTypes.bool.isRequired,
62 appUpdateIsDownloaded: PropTypes.bool.isRequired, 62 appUpdateIsDownloaded: PropTypes.bool.isRequired,
63 nextAppReleaseVersion: PropTypes.string, 63 nextAppReleaseVersion: PropTypes.string,
@@ -69,6 +69,7 @@ class AppLayout extends Component {
69 retryRequiredRequests: PropTypes.func.isRequired, 69 retryRequiredRequests: PropTypes.func.isRequired,
70 areRequiredRequestsLoading: PropTypes.bool.isRequired, 70 areRequiredRequestsLoading: PropTypes.bool.isRequired,
71 isDelayAppScreenVisible: PropTypes.bool.isRequired, 71 isDelayAppScreenVisible: PropTypes.bool.isRequired,
72 hasActivatedTrial: PropTypes.bool.isRequired,
72 }; 73 };
73 74
74 static defaultProps = { 75 static defaultProps = {
@@ -88,7 +89,6 @@ class AppLayout extends Component {
88 sidebar, 89 sidebar,
89 services, 90 services,
90 children, 91 children,
91 // isOnline,
92 news, 92 news,
93 showServicesUpdatedInfoBar, 93 showServicesUpdatedInfoBar,
94 appUpdateIsDownloaded, 94 appUpdateIsDownloaded,
@@ -101,6 +101,7 @@ class AppLayout extends Component {
101 retryRequiredRequests, 101 retryRequiredRequests,
102 areRequiredRequestsLoading, 102 areRequiredRequestsLoading,
103 isDelayAppScreenVisible, 103 isDelayAppScreenVisible,
104 hasActivatedTrial,
104 } = this.props; 105 } = this.props;
105 106
106 const { intl } = this.context; 107 const { intl } = this.context;
@@ -125,26 +126,20 @@ class AppLayout extends Component {
125 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 126 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
126 </InfoBar> 127 </InfoBar>
127 ))} 128 ))}
128 {/* {!isOnline && ( 129 {hasActivatedTrial && (
129 <InfoBar 130 <TrialActivationInfoBar />
130 type="danger" 131 )}
131 sticky
132 >
133 <span className="mdi mdi-flash" />
134 {intl.formatMessage(globalMessages.notConnectedToTheInternet)}
135 </InfoBar>
136 )} */}
137 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 132 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
138 <InfoBar 133 <InfoBar
139 type="danger" 134 type="danger"
140 ctaLabel="Try again" 135 ctaLabel="Try again"
141 ctaLoading={areRequiredRequestsLoading} 136 ctaLoading={areRequiredRequestsLoading}
142 sticky 137 sticky
143 onClick={retryRequiredRequests} 138 onClick={retryRequiredRequests}
144 > 139 >
145 <span className="mdi mdi-flash" /> 140 <span className="mdi mdi-flash" />
146 {intl.formatMessage(messages.requiredRequestsFailed)} 141 {intl.formatMessage(messages.requiredRequestsFailed)}
147 </InfoBar> 142 </InfoBar>
148 )} 143 )}
149 {showServicesUpdatedInfoBar && ( 144 {showServicesUpdatedInfoBar && (
150 <InfoBar 145 <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..b71ddd8e6 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,30 @@ 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
90 console.log('hasActivatedTrial', hasActivatedTrial, (userHasCompletedSignup || hasActivatedTrial));
91
54 return ( 92 return (
55 <div className="services"> 93 <div className="services">
94 {(userHasCompletedSignup || hasActivatedTrial) && (
95 <div className={classes.confettiContainer}>
96 <Confetti
97 width={window.width}
98 height={window.height}
99 numberOfPieces={showConfetti ? 200 : 0}
100 />
101 </div>
102 )}
56 {services.length === 0 && ( 103 {services.length === 0 && (
57 <Appear 104 <Appear
58 timeout={1500} 105 timeout={1500}
@@ -89,6 +136,7 @@ export default @observer class Services extends Component {
89 }, 136 },
90 redirect: false, 137 redirect: false,
91 })} 138 })}
139 upgrade={() => openSettings({ path: 'user' })}
92 /> 140 />
93 ))} 141 ))}
94 </div> 142 </div>
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 3f6964b6b..079d50380 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, H2, H1, H3,
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: {
@@ -20,8 +24,8 @@ const messages = defineMessages({
20 defaultMessage: '!!!Your Subscription', 24 defaultMessage: '!!!Your Subscription',
21 }, 25 },
22 headlineUpgrade: { 26 headlineUpgrade: {
23 id: 'settings.account.headlineUpgrade', 27 id: 'settings.account.headlineTrialUpgrade',
24 defaultMessage: '!!!Upgrade your Account', 28 defaultMessage: '!!!Get the free 14 day Franz Professional Trial',
25 }, 29 },
26 headlineDangerZone: { 30 headlineDangerZone: {
27 id: 'settings.account.headlineDangerZone', 31 id: 'settings.account.headlineDangerZone',
@@ -71,6 +75,22 @@ 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 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 {
@@ -110,6 +130,13 @@ export default @observer class AccountDashboard extends Component {
110 } = this.props; 130 } = this.props;
111 const { intl } = this.context; 131 const { intl } = this.context;
112 132
133 let planName = '';
134
135 if (user.team && user.team.plan) {
136 planName = i18nPlanName(user.team.plan, intl);
137 console.log(planName);
138 }
139
113 return ( 140 return (
114 <div className="settings__main"> 141 <div className="settings__main">
115 <div className="settings__header"> 142 <div className="settings__header">
@@ -135,82 +162,123 @@ export default @observer class AccountDashboard extends Component {
135 )} 162 )}
136 163
137 {!userInfoRequestFailed && ( 164 {!userInfoRequestFailed && (
138 <Fragment> 165 <>
139 {!isLoading && ( 166 {!isLoading && (
140 <div className="account"> 167 <>
141 <div className="account__box account__box--flex"> 168 <div className="account">
142 <div className="account__avatar"> 169 <div className="account__box account__box--flex">
143 <img 170 <div className="account__avatar">
144 src="./assets/images/logo.svg" 171 <img
145 alt="" 172 src="./assets/images/logo.svg"
146 /> 173 alt=""
147 </div> 174 />
148 <div className="account__info"> 175 </div>
149 <h2> 176 <div className="account__info">
150 <span className="username">{`${user.firstname} ${user.lastname}`}</span> 177 <H1>
178 <span className="username">{`${user.firstname} ${user.lastname}`}</span>
179 {user.isPremium && (
180 <>
181 {' '}
182 <ProBadge />
183 {/* <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> */}
184 </>
185 )}
186 </H1>
187 <p>
188 {user.organization && `${user.organization}, `}
189 {user.email}
190 </p>
151 {user.isPremium && ( 191 {user.isPremium && (
192 <div className="manage-user-links">
193 <Button
194 label={intl.formatMessage(messages.accountEditButton)}
195 className="franz-form__button--inverted"
196 onClick={openEditAccount}
197 />
198 {/* {user.isSubscriptionOwner && (
199 <>
200 <Button
201 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
202 className="franz-form__button--inverted"
203 onClick={openBilling}
204 />
205 <Button
206 label={intl.formatMessage(messages.invoicesButton)}
207 className="franz-form__button--inverted"
208 onClick={openInvoices}
209 />
210 </>
211 )} */}
212 </div>
213 )}
214 </div>
215 {!user.isPremium && (
216 <Button
217 label={intl.formatMessage(messages.accountEditButton)}
218 className="franz-form__button--inverted"
219 onClick={openEditAccount}
220 />
221 )}
222 </div>
223 </div>
224 {user.isSubscriptionOwner && (
225 <div className="account">
226 <div className="account__box">
227 <H2>
228 {intl.formatMessage(messages.yourLicense)}
229 </H2>
230 <H3>
231 {planName}
232 {user.team.isTrial && (
233 <>
234 {' – '}
235 {intl.formatMessage(messages.trial)}
236 </>
237 )}
238 </H3>
239 {user.team.isTrial && (
152 <> 240 <>
153 {' '} 241 <p>
154 <ProBadge /> 242 {intl.formatMessage(messages.trialEndsIn, {
155 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> 243 duration: moment.duration(moment().diff(user.team.trialEnd)).humanize(),
244 })}
245 </p>
246 <p>
247 {intl.formatMessage(messages.trialUpdateBillingInformation, {
248 license: planName,
249 })}
250 </p>
156 </> 251 </>
157 )} 252 )}
158 </h2>
159 {user.organization && `${user.organization}, `}
160 {user.email}
161 {user.isPremium && (
162 <div className="manage-user-links"> 253 <div className="manage-user-links">
163 <Button 254 <Button
164 label={intl.formatMessage(messages.accountEditButton)} 255 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
165 className="franz-form__button--inverted" 256 className="franz-form__button--inverted"
166 onClick={openEditAccount} 257 onClick={openBilling}
258 />
259 <Button
260 label={intl.formatMessage(messages.invoicesButton)}
261 className="franz-form__button--inverted"
262 onClick={openInvoices}
167 /> 263 />
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> 264 </div>
183 )} 265 </div>
184 </div> 266 </div>
185 {!user.isPremium && ( 267 )}
186 <Button 268 {!user.isPremium && (
187 label={intl.formatMessage(messages.accountEditButton)} 269 <div className="account franz-form">
188 className="franz-form__button--inverted" 270 <div className="account__box">
189 onClick={openEditAccount} 271 <H2>{intl.formatMessage(messages.headlineUpgrade)}</H2>
190 /> 272 <SubscriptionForm />
191 )} 273 </div>
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> 274 </div>
207 </div> 275 )}
208 ) 276 </>
209 )} 277 )}
210 278
211 <div className="account franz-form"> 279 <div className="account franz-form">
212 <div className="account__box"> 280 <div className="account__box">
213 <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> 281 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2>
214 {!isDeleteAccountSuccessful && ( 282 {!isDeleteAccountSuccessful && (
215 <div className="account__subscription"> 283 <div className="account__subscription">
216 <p>{intl.formatMessage(messages.deleteInfo)}</p> 284 <p>{intl.formatMessage(messages.deleteInfo)}</p>
@@ -227,7 +295,7 @@ export default @observer class AccountDashboard extends Component {
227 )} 295 )}
228 </div> 296 </div>
229 </div> 297 </div>
230 </Fragment> 298 </>
231 )} 299 )}
232 </div> 300 </div>
233 <ReactTooltip place="right" type="dark" effect="solid" /> 301 <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/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
index 50f1e0522..0c630f047 100644
--- a/src/components/subscription/SubscriptionForm.js
+++ b/src/components/subscription/SubscriptionForm.js
@@ -1,214 +1,94 @@
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 } 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';
11import { FeatureItem } from '../ui/FeatureItem';
12 12
13const messages = defineMessages({ 13const messages = defineMessages({
14 submitButtonLabel: { 14 submitButtonLabel: {
15 id: 'subscription.submit.label', 15 id: 'subscription.cta.activateTrial',
16 defaultMessage: '!!!Support the development of Franz', 16 defaultMessage: '!!!Yes, upgrade my account to Franz Professional',
17 },
18 paymentSessionError: {
19 id: 'subscription.paymentSessionError',
20 defaultMessage: '!!!Could not initialize payment form',
21 },
22 typeFree: {
23 id: 'subscription.type.free',
24 defaultMessage: '!!!free',
25 },
26 typeMonthly: {
27 id: 'subscription.type.month',
28 defaultMessage: '!!!month',
29 },
30 typeYearly: {
31 id: 'subscription.type.year',
32 defaultMessage: '!!!year',
33 }, 17 },
34 includedFeatures: { 18 includedFeatures: {
35 id: 'subscription.includedFeatures', 19 id: 'subscription.includedProFeatures',
36 defaultMessage: '!!!The Franz Premium Supporter Account includes', 20 defaultMessage: '!!!The Franz Professional Plan includes:',
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 }, 21 },
46 proxy: { 22 noStringsAttachedHeadline: {
47 id: 'subscription.features.proxy', 23 id: 'pricing.trial.terms.headline',
48 defaultMessage: '!!!Proxy support for services', 24 defaultMessage: '!!!No strings attached',
49 }, 25 },
50 spellchecker: { 26 noCreditCard: {
51 id: 'subscription.features.spellchecker', 27 id: 'pricing.trial.terms.noCreditCard',
52 defaultMessage: '!!!Support for Spellchecker', 28 defaultMessage: '!!!No credit card required',
53 }, 29 },
54 workspaces: { 30 automaticTrialEnd: {
55 id: 'subscription.features.workspaces', 31 id: 'pricing.trial.terms.automaticTrialEnd',
56 defaultMessage: '!!!Organize your services in workspaces', 32 defaultMessage: '!!!Your free trial ends automatically after 14 days',
57 }, 33 },
58 ads: { 34});
59 id: 'subscription.features.ads', 35
60 defaultMessage: '!!!No ads, ever!', 36const styles = () => ({
61 }, 37 activateTrialButton: {
62 comingSoon: { 38 margin: [40, 0, 50],
63 id: 'subscription.features.comingSoon',
64 defaultMessage: '!!!coming soon',
65 }, 39 },
66 euTaxInfo: { 40 keyTerms: {
67 id: 'subscription.euTaxInfo', 41 marginTop: 20,
68 defaultMessage: '!!!EU residents: local sales tax may apply',
69 }, 42 },
70}); 43});
71 44
72export default @observer class SubscriptionForm extends Component { 45export default @observer @injectSheet(styles) class SubscriptionForm extends Component {
73 static propTypes = { 46 static propTypes = {
74 plan: MobxPropTypes.objectOrObservableObject.isRequired, 47 activateTrial: PropTypes.func.isRequired,
75 isLoading: PropTypes.bool.isRequired, 48 isActivatingTrial: PropTypes.bool.isRequired,
76 handlePayment: PropTypes.func.isRequired, 49 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 }; 50 };
91 51
92 static contextTypes = { 52 static contextTypes = {
93 intl: intlShape, 53 intl: intlShape,
94 }; 54 };
95 55
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() { 56 render() {
134 const { 57 const {
135 isLoading, 58 isActivatingTrial,
136 isCreatingHostedPage, 59 activateTrial,
137 handlePayment, 60 classes,
138 retryPlanRequest,
139 error,
140 showSkipOption,
141 skipAction,
142 skipButtonLabel,
143 hideInfo,
144 } = this.props; 61 } = this.props;
145 const { intl } = this.context; 62 const { intl } = this.context;
146 63
147 if (error) { 64 console.log('isActivatingTrial', isActivatingTrial);
148 return (
149 <Button
150 label="Reload"
151 onClick={retryPlanRequest}
152 isLoaded={!isLoading}
153 />
154 );
155 }
156 65
157 return ( 66 return (
158 <Loader loaded={!isLoading}> 67 <>
159 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> 68 <H3 className={classes.keyTerms}>
160 {!hideInfo && ( 69 {intl.formatMessage(messages.noStringsAttachedHeadline)}
161 <div className="subscription__premium-info"> 70 </H3>
162 <p> 71 <ul>
163 <strong>{intl.formatMessage(messages.includedFeatures)}</strong> 72 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
164 </p> 73 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
165 <div className="subscription"> 74 </ul>
166 <ul className="subscription__premium-features"> 75
167 <li>{intl.formatMessage(messages.onpremise)}</li> 76 <Button
168 <li> 77 label={intl.formatMessage(messages.submitButtonLabel)}
169 {intl.formatMessage(messages.noInterruptions)} 78 className={classes.activateTrialButton}
170 </li> 79 busy={isActivatingTrial}
171 <li> 80 onClick={activateTrial}
172 {intl.formatMessage(messages.spellchecker)} 81 stretch
173 </li> 82 />
174 <li> 83 <div className="subscription__premium-info">
175 {intl.formatMessage(messages.proxy)} 84 <H3>
176 </li> 85 {intl.formatMessage(messages.includedFeatures)}
177 <li> 86 </H3>
178 {intl.formatMessage(messages.workspaces)} 87 <div className="subscription">
179 </li> 88 <FeatureList />
180 <li>
181 {intl.formatMessage(messages.ads)}
182 </li>
183 </ul>
184 </div>
185 </div> 89 </div>
186 )} 90 </div>
187 <Fragment> 91 </>
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 ); 92 );
213 } 93 }
214} 94}
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
new file mode 100644
index 000000000..a63f5f7b5
--- /dev/null
+++ b/src/components/ui/FeatureItem.js
@@ -0,0 +1,36 @@
1import React from 'react';
2import injectSheet from 'react-jss';
3import { Icon } from '@meetfranz/ui';
4import classnames from 'classnames';
5
6const styles = theme => ({
7 featureItem: {
8 borderBottom: [1, 'solid', theme.legacyStyles.themeGrayDark],
9 padding: [8, 0],
10 display: 'flex',
11 alignItems: 'center',
12 },
13 featureIcon: {
14 fill: theme.brandSuccess,
15 marginRight: 10,
16 },
17});
18
19export const FeatureItem = injectSheet(styles)(({
20 classes, className, name, icon,
21}) => (
22 <li className={classnames({
23 [classes.featureItem]: true,
24 [className]: className,
25 })}
26 >
27 {icon ? (
28 <span className={classes.featureIcon}>{icon}</span>
29 ) : (
30 <Icon icon="mdiCheckCircle" className={classes.featureIcon} size={1.5} />
31 )}
32 {name}
33 </li>
34));
35
36export 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/config.js b/src/config.js
index 5bc318545..edcae9df1 100644
--- a/src/config.js
+++ b/src/config.js
@@ -45,16 +45,16 @@ export const DEFAULT_APP_SETTINGS = {
45}; 45};
46 46
47export const DEFAULT_FEATURES_CONFIG = { 47export const DEFAULT_FEATURES_CONFIG = {
48 isSpellcheckerPremiumFeature: false, 48 isSpellcheckerIncludedInCurrentPlan: true,
49 needToWaitToProceed: false, 49 needToWaitToProceed: false,
50 needToWaitToProceedConfig: { 50 needToWaitToProceedConfig: {
51 delayOffset: ms('1h'), 51 delayOffset: ms('1h'),
52 wait: ms('10s'), 52 wait: ms('10s'),
53 }, 53 },
54 isServiceProxyEnabled: false, 54 isServiceProxyEnabled: false,
55 isServiceProxyPremiumFeature: true, 55 isServiceProxyIncludedInCurrentPlan: false,
56 isAnnouncementsEnabled: true, 56 isAnnouncementsEnabled: true,
57 isWorkspacePremiumFeature: true, 57 isWorkspaceIncludedInCurrentPlan: true,
58 isWorkspaceEnabled: false, 58 isWorkspaceEnabled: false,
59}; 59};
60 60
@@ -67,6 +67,7 @@ export const DEFAULT_WINDOW_OPTIONS = {
67 67
68export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs'; 68export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs';
69export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; 69export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate';
70export const FRANZ_DEV_DOCS = 'http://bit.ly/franz-dev-hub';
70 71
71export const FILE_SYSTEM_SETTINGS_TYPES = [ 72export const FILE_SYSTEM_SETTINGS_TYPES = [
72 'app', 73 'app',
@@ -83,3 +84,27 @@ export const ALLOWED_PROTOCOLS = [
83 'http:', 84 'http:',
84 'ftp:', 85 'ftp:',
85]; 86];
87
88export const PLANS = {
89 PERSONAL_MONTHLY: 'PERSONAL_MONTHLY',
90 PERSONAL_YEARLY: 'PERSONAL_YEARLY',
91 PRO_MONTHLY: 'PRO_MONTHLY',
92 PRO_YEARLY: 'PRO_YEARLY',
93 LEGACY: 'LEGACY',
94 FREE: 'FREE',
95};
96
97export const PLANS_MAPPING = {
98 'franz-personal-monthly': PLANS.PERSONAL_MONTHLY,
99 'franz-personal-yearly': PLANS.PERSONAL_YEARLY,
100 'franz-pro-monthly': PLANS.PRO_MONTHLY,
101 'franz-pro-yearly': PLANS.PRO_YEARLY,
102 'franz-supporter-license': PLANS.LEGACY,
103 'franz-supporter-license-x1': PLANS.LEGACY,
104 'franz-supporter-license-x2': PLANS.LEGACY,
105 'franz-supporter-license-year': PLANS.LEGACY,
106 'franz-supporter-license-year-x1': PLANS.LEGACY,
107 'franz-supporter-license-year-x2': PLANS.LEGACY,
108 'franz-supporter-license-year-2019': PLANS.LEGACY,
109 free: PLANS.FREE,
110};
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/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/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js
index aa1166f5e..7486cc498 100644
--- a/src/containers/subscription/SubscriptionFormScreen.js
+++ b/src/containers/subscription/SubscriptionFormScreen.js
@@ -74,28 +74,14 @@ export default @inject('stores', 'actions') @observer class SubscriptionFormScre
74 74
75 render() { 75 render() {
76 const { 76 const {
77 content,
78 actions, 77 actions,
79 stores, 78 stores,
80 showSkipOption,
81 skipAction,
82 skipButtonLabel,
83 hideInfo,
84 } = this.props; 79 } = this.props;
85 return ( 80 return (
86 <SubscriptionForm 81 <SubscriptionForm
87 plan={stores.payment.plan} 82 plan={stores.payment.plan}
88 isLoading={stores.payment.plansRequest.isExecuting} 83 activateTrial={() => actions.user.activateTrial({ planId: stores.features.features.defaultTrialPlan })}
89 retryPlanRequest={() => stores.payment.plansRequest.reload()} 84 isActivatingTrial={stores.user.activateTrialRequest.isExecuting || stores.user.getUserInfoRequest.isExecuting}
90 isCreatingHostedPage={stores.payment.createHostedPageRequest.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 /> 85 />
100 ); 86 );
101 } 87 }
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/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..caa73619e 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..37a4457af
--- /dev/null
+++ b/src/helpers/plan-helpers.js
@@ -0,0 +1,43 @@
1import { defineMessages } from 'react-intl';
2import { PLANS_MAPPING, PLANS } from '../config';
3
4const messages = defineMessages({
5 [PLANS.PRO_YEARLY]: {
6 id: 'pricing.plan.pro-yearly',
7 defaultMessage: '!!!Franz Professional Yearly',
8 },
9 [PLANS.PRO_MONTHLY]: {
10 id: 'pricing.plan.pro-monthly',
11 defaultMessage: '!!!Franz Professional Monthly',
12 },
13 [PLANS.PERSONAL_YEARLY]: {
14 id: 'pricing.plan.personal-yearly',
15 defaultMessage: '!!!Franz Personal Yearly',
16 },
17 [PLANS.PERSONAL_MONTHLY]: {
18 id: 'pricing.plan.personal-monthly',
19 defaultMessage: '!!!Franz Personal Monthly',
20 },
21 [PLANS.FREE]: {
22 id: 'pricing.plan.free',
23 defaultMessage: '!!!Franz Free',
24 },
25 [PLANS.LEGACY]: {
26 id: 'pricing.plan.legacy',
27 defaultMessage: '!!!Franz Premium',
28 },
29});
30
31export function i18nPlanName(planId, intl) {
32 if (!planId) {
33 throw new Error('planId is required');
34 }
35
36 if (!intl) {
37 throw new Error('intl context is required');
38 }
39
40 const plan = PLANS_MAPPING[planId];
41
42 return intl.formatMessage(messages[plan]);
43}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index eca3062c2..bdb1def11 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 }, 450 },
451 "file": "src/components/auth/Pricing.js", 451 "file": "src/components/auth/Pricing.js",
452 "id": "pricing.submit.label", 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
476 },
477 "file": "src/components/auth/Pricing.js",
478 "id": "pricing.trial.terms.automaticTrialEnd",
453 "start": { 479 "start": {
454 "column": 21, 480 "column": 21,
455 "line": 21 481 "line": 31
456 } 482 }
457 }, 483 },
458 { 484 {
459 "defaultMessage": "!!!I don't want to support the development of Franz.", 485 "defaultMessage": "!!!Sorry, we could not activate your trial!",
460 "end": { 486 "end": {
461 "column": 3, 487 "column": 3,
462 "line": 28 488 "line": 38
463 }, 489 },
464 "file": "src/components/auth/Pricing.js", 490 "file": "src/components/auth/Pricing.js",
465 "id": "pricing.link.skipPayment", 491 "id": "pricing.trial.error",
466 "start": { 492 "start": {
467 "column": 15, 493 "column": 19,
468 "line": 25 494 "line": 35
495 }
496 },
497 {
498 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional",
499 "end": {
500 "column": 3,
501 "line": 42
502 },
503 "file": "src/components/auth/Pricing.js",
504 "id": "pricing.trial.cta.accept",
505 "start": {
506 "column": 13,
507 "line": 39
508 }
509 },
510 {
511 "defaultMessage": "!!!Continue to Franz",
512 "end": {
513 "column": 3,
514 "line": 46
515 },
516 "file": "src/components/auth/Pricing.js",
517 "id": "pricing.trial.cta.skip",
518 "start": {
519 "column": 11,
520 "line": 43
521 }
522 },
523 {
524 "defaultMessage": "!!!Franz Professional includes:",
525 "end": {
526 "column": 3,
527 "line": 50
528 },
529 "file": "src/components/auth/Pricing.js",
530 "id": "pricing.trial.features.headline",
531 "start": {
532 "column": 20,
533 "line": 47
469 } 534 }
470 } 535 }
471 ], 536 ],
@@ -669,39 +734,39 @@
669 "defaultMessage": "!!!Your services have been updated.", 734 "defaultMessage": "!!!Your services have been updated.",
670 "end": { 735 "end": {
671 "column": 3, 736 "column": 3,
672 "line": 29 737 "line": 30
673 }, 738 },
674 "file": "src/components/layout/AppLayout.js", 739 "file": "src/components/layout/AppLayout.js",
675 "id": "infobar.servicesUpdated", 740 "id": "infobar.servicesUpdated",
676 "start": { 741 "start": {
677 "column": 19, 742 "column": 19,
678 "line": 26 743 "line": 27
679 } 744 }
680 }, 745 },
681 { 746 {
682 "defaultMessage": "!!!Reload services", 747 "defaultMessage": "!!!Reload services",
683 "end": { 748 "end": {
684 "column": 3, 749 "column": 3,
685 "line": 33 750 "line": 34
686 }, 751 },
687 "file": "src/components/layout/AppLayout.js", 752 "file": "src/components/layout/AppLayout.js",
688 "id": "infobar.buttonReloadServices", 753 "id": "infobar.buttonReloadServices",
689 "start": { 754 "start": {
690 "column": 24, 755 "column": 24,
691 "line": 30 756 "line": 31
692 } 757 }
693 }, 758 },
694 { 759 {
695 "defaultMessage": "!!!Could not load services and user information", 760 "defaultMessage": "!!!Could not load services and user information",
696 "end": { 761 "end": {
697 "column": 3, 762 "column": 3,
698 "line": 37 763 "line": 38
699 }, 764 },
700 "file": "src/components/layout/AppLayout.js", 765 "file": "src/components/layout/AppLayout.js",
701 "id": "infobar.requiredRequestsFailed", 766 "id": "infobar.requiredRequestsFailed",
702 "start": { 767 "start": {
703 "column": 26, 768 "column": 26,
704 "line": 34 769 "line": 35
705 } 770 }
706 } 771 }
707 ], 772 ],
@@ -894,29 +959,99 @@
894 { 959 {
895 "descriptors": [ 960 "descriptors": [
896 { 961 {
897 "defaultMessage": "!!!Welcome to Franz", 962 "defaultMessage": "!!!You have reached your service limit.",
898 "end": { 963 "end": {
899 "column": 3, 964 "column": 3,
900 "line": 14 965 "line": 14
901 }, 966 },
967 "file": "src/components/services/content/ServiceRestricted.js",
968 "id": "service.restrictedHandler.serviceLimit.headline",
969 "start": {
970 "column": 24,
971 "line": 11
972 }
973 },
974 {
975 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
976 "end": {
977 "column": 3,
978 "line": 18
979 },
980 "file": "src/components/services/content/ServiceRestricted.js",
981 "id": "service.restrictedHandler.serviceLimit.text",
982 "start": {
983 "column": 20,
984 "line": 15
985 }
986 },
987 {
988 "defaultMessage": "!!!Franz Professional Plan required",
989 "end": {
990 "column": 3,
991 "line": 22
992 },
993 "file": "src/components/services/content/ServiceRestricted.js",
994 "id": "service.restrictedHandler.customUrl.headline",
995 "start": {
996 "column": 21,
997 "line": 19
998 }
999 },
1000 {
1001 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
1002 "end": {
1003 "column": 3,
1004 "line": 26
1005 },
1006 "file": "src/components/services/content/ServiceRestricted.js",
1007 "id": "service.restrictedHandler.customUrl.text",
1008 "start": {
1009 "column": 17,
1010 "line": 23
1011 }
1012 },
1013 {
1014 "defaultMessage": "!!!Upgrade Account",
1015 "end": {
1016 "column": 3,
1017 "line": 30
1018 },
1019 "file": "src/components/services/content/ServiceRestricted.js",
1020 "id": "service.restrictedHandler.action",
1021 "start": {
1022 "column": 10,
1023 "line": 27
1024 }
1025 }
1026 ],
1027 "path": "src/components/services/content/ServiceRestricted.json"
1028 },
1029 {
1030 "descriptors": [
1031 {
1032 "defaultMessage": "!!!Welcome to Franz",
1033 "end": {
1034 "column": 3,
1035 "line": 17
1036 },
902 "file": "src/components/services/content/Services.js", 1037 "file": "src/components/services/content/Services.js",
903 "id": "services.welcome", 1038 "id": "services.welcome",
904 "start": { 1039 "start": {
905 "column": 11, 1040 "column": 11,
906 "line": 11 1041 "line": 14
907 } 1042 }
908 }, 1043 },
909 { 1044 {
910 "defaultMessage": "!!!Get started", 1045 "defaultMessage": "!!!Get started",
911 "end": { 1046 "end": {
912 "column": 3, 1047 "column": 3,
913 "line": 18 1048 "line": 21
914 }, 1049 },
915 "file": "src/components/services/content/Services.js", 1050 "file": "src/components/services/content/Services.js",
916 "id": "services.getStarted", 1051 "id": "services.getStarted",
917 "start": { 1052 "start": {
918 "column": 14, 1053 "column": 14,
919 "line": 15 1054 "line": 18
920 } 1055 }
921 } 1056 }
922 ], 1057 ],
@@ -1107,195 +1242,247 @@
1107 "defaultMessage": "!!!Account", 1242 "defaultMessage": "!!!Account",
1108 "end": { 1243 "end": {
1109 "column": 3, 1244 "column": 3,
1110 "line": 17 1245 "line": 19
1111 }, 1246 },
1112 "file": "src/components/settings/account/AccountDashboard.js", 1247 "file": "src/components/settings/account/AccountDashboard.js",
1113 "id": "settings.account.headline", 1248 "id": "settings.account.headline",
1114 "start": { 1249 "start": {
1115 "column": 12, 1250 "column": 12,
1116 "line": 14 1251 "line": 16
1117 } 1252 }
1118 }, 1253 },
1119 { 1254 {
1120 "defaultMessage": "!!!Your Subscription", 1255 "defaultMessage": "!!!Your Subscription",
1121 "end": { 1256 "end": {
1122 "column": 3, 1257 "column": 3,
1123 "line": 21 1258 "line": 23
1124 }, 1259 },
1125 "file": "src/components/settings/account/AccountDashboard.js", 1260 "file": "src/components/settings/account/AccountDashboard.js",
1126 "id": "settings.account.headlineSubscription", 1261 "id": "settings.account.headlineSubscription",
1127 "start": { 1262 "start": {
1128 "column": 24, 1263 "column": 24,
1129 "line": 18 1264 "line": 20
1130 } 1265 }
1131 }, 1266 },
1132 { 1267 {
1133 "defaultMessage": "!!!Upgrade your Account", 1268 "defaultMessage": "!!!Upgrade your Account",
1134 "end": { 1269 "end": {
1135 "column": 3, 1270 "column": 3,
1136 "line": 25 1271 "line": 27
1137 }, 1272 },
1138 "file": "src/components/settings/account/AccountDashboard.js", 1273 "file": "src/components/settings/account/AccountDashboard.js",
1139 "id": "settings.account.headlineUpgrade", 1274 "id": "settings.account.headlineUpgrade",
1140 "start": { 1275 "start": {
1141 "column": 19, 1276 "column": 19,
1142 "line": 22 1277 "line": 24
1143 } 1278 }
1144 }, 1279 },
1145 { 1280 {
1146 "defaultMessage": "!!Danger Zone", 1281 "defaultMessage": "!!Danger Zone",
1147 "end": { 1282 "end": {
1148 "column": 3, 1283 "column": 3,
1149 "line": 29 1284 "line": 31
1150 }, 1285 },
1151 "file": "src/components/settings/account/AccountDashboard.js", 1286 "file": "src/components/settings/account/AccountDashboard.js",
1152 "id": "settings.account.headlineDangerZone", 1287 "id": "settings.account.headlineDangerZone",
1153 "start": { 1288 "start": {
1154 "column": 22, 1289 "column": 22,
1155 "line": 26 1290 "line": 28
1156 } 1291 }
1157 }, 1292 },
1158 { 1293 {
1159 "defaultMessage": "!!!Manage your subscription", 1294 "defaultMessage": "!!!Manage your subscription",
1160 "end": { 1295 "end": {
1161 "column": 3, 1296 "column": 3,
1162 "line": 33 1297 "line": 35
1163 }, 1298 },
1164 "file": "src/components/settings/account/AccountDashboard.js", 1299 "file": "src/components/settings/account/AccountDashboard.js",
1165 "id": "settings.account.manageSubscription.label", 1300 "id": "settings.account.manageSubscription.label",
1166 "start": { 1301 "start": {
1167 "column": 33, 1302 "column": 33,
1168 "line": 30 1303 "line": 32
1169 } 1304 }
1170 }, 1305 },
1171 { 1306 {
1172 "defaultMessage": "!!!Basic Account", 1307 "defaultMessage": "!!!Basic Account",
1173 "end": { 1308 "end": {
1174 "column": 3, 1309 "column": 3,
1175 "line": 37 1310 "line": 39
1176 }, 1311 },
1177 "file": "src/components/settings/account/AccountDashboard.js", 1312 "file": "src/components/settings/account/AccountDashboard.js",
1178 "id": "settings.account.accountType.basic", 1313 "id": "settings.account.accountType.basic",
1179 "start": { 1314 "start": {
1180 "column": 20, 1315 "column": 20,
1181 "line": 34 1316 "line": 36
1182 } 1317 }
1183 }, 1318 },
1184 { 1319 {
1185 "defaultMessage": "!!!Premium Supporter Account", 1320 "defaultMessage": "!!!Premium Supporter Account",
1186 "end": { 1321 "end": {
1187 "column": 3, 1322 "column": 3,
1188 "line": 41 1323 "line": 43
1189 }, 1324 },
1190 "file": "src/components/settings/account/AccountDashboard.js", 1325 "file": "src/components/settings/account/AccountDashboard.js",
1191 "id": "settings.account.accountType.premium", 1326 "id": "settings.account.accountType.premium",
1192 "start": { 1327 "start": {
1193 "column": 22, 1328 "column": 22,
1194 "line": 38 1329 "line": 40
1195 } 1330 }
1196 }, 1331 },
1197 { 1332 {
1198 "defaultMessage": "!!!Edit Account", 1333 "defaultMessage": "!!!Edit Account",
1199 "end": { 1334 "end": {
1200 "column": 3, 1335 "column": 3,
1201 "line": 45 1336 "line": 47
1202 }, 1337 },
1203 "file": "src/components/settings/account/AccountDashboard.js", 1338 "file": "src/components/settings/account/AccountDashboard.js",
1204 "id": "settings.account.account.editButton", 1339 "id": "settings.account.account.editButton",
1205 "start": { 1340 "start": {
1206 "column": 21, 1341 "column": 21,
1207 "line": 42 1342 "line": 44
1208 } 1343 }
1209 }, 1344 },
1210 { 1345 {
1211 "defaultMessage": "!!Invoices", 1346 "defaultMessage": "!!Invoices",
1212 "end": { 1347 "end": {
1213 "column": 3, 1348 "column": 3,
1214 "line": 49 1349 "line": 51
1215 }, 1350 },
1216 "file": "src/components/settings/account/AccountDashboard.js", 1351 "file": "src/components/settings/account/AccountDashboard.js",
1217 "id": "settings.account.headlineInvoices", 1352 "id": "settings.account.headlineInvoices",
1218 "start": { 1353 "start": {
1219 "column": 18, 1354 "column": 18,
1220 "line": 46 1355 "line": 48
1221 } 1356 }
1222 }, 1357 },
1223 { 1358 {
1224 "defaultMessage": "!!!Download", 1359 "defaultMessage": "!!!Download",
1225 "end": { 1360 "end": {
1226 "column": 3, 1361 "column": 3,
1227 "line": 53 1362 "line": 55
1228 }, 1363 },
1229 "file": "src/components/settings/account/AccountDashboard.js", 1364 "file": "src/components/settings/account/AccountDashboard.js",
1230 "id": "settings.account.invoiceDownload", 1365 "id": "settings.account.invoiceDownload",
1231 "start": { 1366 "start": {
1232 "column": 19, 1367 "column": 19,
1233 "line": 50 1368 "line": 52
1234 } 1369 }
1235 }, 1370 },
1236 { 1371 {
1237 "defaultMessage": "!!!Could not load user information", 1372 "defaultMessage": "!!!Could not load user information",
1238 "end": { 1373 "end": {
1239 "column": 3, 1374 "column": 3,
1240 "line": 57 1375 "line": 59
1241 }, 1376 },
1242 "file": "src/components/settings/account/AccountDashboard.js", 1377 "file": "src/components/settings/account/AccountDashboard.js",
1243 "id": "settings.account.userInfoRequestFailed", 1378 "id": "settings.account.userInfoRequestFailed",
1244 "start": { 1379 "start": {
1245 "column": 25, 1380 "column": 25,
1246 "line": 54 1381 "line": 56
1247 } 1382 }
1248 }, 1383 },
1249 { 1384 {
1250 "defaultMessage": "!!!Try again", 1385 "defaultMessage": "!!!Try again",
1251 "end": { 1386 "end": {
1252 "column": 3, 1387 "column": 3,
1253 "line": 61 1388 "line": 63
1254 }, 1389 },
1255 "file": "src/components/settings/account/AccountDashboard.js", 1390 "file": "src/components/settings/account/AccountDashboard.js",
1256 "id": "settings.account.tryReloadUserInfoRequest", 1391 "id": "settings.account.tryReloadUserInfoRequest",
1257 "start": { 1392 "start": {
1258 "column": 28, 1393 "column": 28,
1259 "line": 58 1394 "line": 60
1260 } 1395 }
1261 }, 1396 },
1262 { 1397 {
1263 "defaultMessage": "!!!Delete account", 1398 "defaultMessage": "!!!Delete account",
1264 "end": { 1399 "end": {
1265 "column": 3, 1400 "column": 3,
1266 "line": 65 1401 "line": 67
1267 }, 1402 },
1268 "file": "src/components/settings/account/AccountDashboard.js", 1403 "file": "src/components/settings/account/AccountDashboard.js",
1269 "id": "settings.account.deleteAccount", 1404 "id": "settings.account.deleteAccount",
1270 "start": { 1405 "start": {
1271 "column": 17, 1406 "column": 17,
1272 "line": 62 1407 "line": 64
1273 } 1408 }
1274 }, 1409 },
1275 { 1410 {
1276 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.", 1411 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.",
1277 "end": { 1412 "end": {
1278 "column": 3, 1413 "column": 3,
1279 "line": 69 1414 "line": 71
1280 }, 1415 },
1281 "file": "src/components/settings/account/AccountDashboard.js", 1416 "file": "src/components/settings/account/AccountDashboard.js",
1282 "id": "settings.account.deleteInfo", 1417 "id": "settings.account.deleteInfo",
1283 "start": { 1418 "start": {
1284 "column": 14, 1419 "column": 14,
1285 "line": 66 1420 "line": 68
1286 } 1421 }
1287 }, 1422 },
1288 { 1423 {
1289 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", 1424 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!",
1290 "end": { 1425 "end": {
1291 "column": 3, 1426 "column": 3,
1292 "line": 73 1427 "line": 75
1293 }, 1428 },
1294 "file": "src/components/settings/account/AccountDashboard.js", 1429 "file": "src/components/settings/account/AccountDashboard.js",
1295 "id": "settings.account.deleteEmailSent", 1430 "id": "settings.account.deleteEmailSent",
1296 "start": { 1431 "start": {
1297 "column": 19, 1432 "column": 19,
1298 "line": 70 1433 "line": 72
1434 }
1435 },
1436 {
1437 "defaultMessage": "!!!Free Trial",
1438 "end": {
1439 "column": 3,
1440 "line": 79
1441 },
1442 "file": "src/components/settings/account/AccountDashboard.js",
1443 "id": "settings.account.trial",
1444 "start": {
1445 "column": 9,
1446 "line": 76
1447 }
1448 },
1449 {
1450 "defaultMessage": "!!!Your license:",
1451 "end": {
1452 "column": 3,
1453 "line": 83
1454 },
1455 "file": "src/components/settings/account/AccountDashboard.js",
1456 "id": "settings.account.yourLicense",
1457 "start": {
1458 "column": 15,
1459 "line": 80
1460 }
1461 },
1462 {
1463 "defaultMessage": "!!!Your free trial ends in {duration}.",
1464 "end": {
1465 "column": 3,
1466 "line": 87
1467 },
1468 "file": "src/components/settings/account/AccountDashboard.js",
1469 "id": "settings.account.trialEndsIn",
1470 "start": {
1471 "column": 15,
1472 "line": 84
1473 }
1474 },
1475 {
1476 "defaultMessage": "!!!Please update your billing info to continue using {license} after your trial period.",
1477 "end": {
1478 "column": 3,
1479 "line": 91
1480 },
1481 "file": "src/components/settings/account/AccountDashboard.js",
1482 "id": "settings.account.trialUpdateBillingInfo",
1483 "start": {
1484 "column": 33,
1485 "line": 88
1299 } 1486 }
1300 } 1487 }
1301 ], 1488 ],
@@ -1307,104 +1494,104 @@
1307 "defaultMessage": "!!!Available services", 1494 "defaultMessage": "!!!Available services",
1308 "end": { 1495 "end": {
1309 "column": 3, 1496 "column": 3,
1310 "line": 16 1497 "line": 17
1311 }, 1498 },
1312 "file": "src/components/settings/navigation/SettingsNavigation.js", 1499 "file": "src/components/settings/navigation/SettingsNavigation.js",
1313 "id": "settings.navigation.availableServices", 1500 "id": "settings.navigation.availableServices",
1314 "start": { 1501 "start": {
1315 "column": 21, 1502 "column": 21,
1316 "line": 13 1503 "line": 14
1317 } 1504 }
1318 }, 1505 },
1319 { 1506 {
1320 "defaultMessage": "!!!Your services", 1507 "defaultMessage": "!!!Your services",
1321 "end": { 1508 "end": {
1322 "column": 3, 1509 "column": 3,
1323 "line": 20 1510 "line": 21
1324 }, 1511 },
1325 "file": "src/components/settings/navigation/SettingsNavigation.js", 1512 "file": "src/components/settings/navigation/SettingsNavigation.js",
1326 "id": "settings.navigation.yourServices", 1513 "id": "settings.navigation.yourServices",
1327 "start": { 1514 "start": {
1328 "column": 16, 1515 "column": 16,
1329 "line": 17 1516 "line": 18
1330 } 1517 }
1331 }, 1518 },
1332 { 1519 {
1333 "defaultMessage": "!!!Your workspaces", 1520 "defaultMessage": "!!!Your workspaces",
1334 "end": { 1521 "end": {
1335 "column": 3, 1522 "column": 3,
1336 "line": 24 1523 "line": 25
1337 }, 1524 },
1338 "file": "src/components/settings/navigation/SettingsNavigation.js", 1525 "file": "src/components/settings/navigation/SettingsNavigation.js",
1339 "id": "settings.navigation.yourWorkspaces", 1526 "id": "settings.navigation.yourWorkspaces",
1340 "start": { 1527 "start": {
1341 "column": 18, 1528 "column": 18,
1342 "line": 21 1529 "line": 22
1343 } 1530 }
1344 }, 1531 },
1345 { 1532 {
1346 "defaultMessage": "!!!Account", 1533 "defaultMessage": "!!!Account",
1347 "end": { 1534 "end": {
1348 "column": 3, 1535 "column": 3,
1349 "line": 28 1536 "line": 29
1350 }, 1537 },
1351 "file": "src/components/settings/navigation/SettingsNavigation.js", 1538 "file": "src/components/settings/navigation/SettingsNavigation.js",
1352 "id": "settings.navigation.account", 1539 "id": "settings.navigation.account",
1353 "start": { 1540 "start": {
1354 "column": 11, 1541 "column": 11,
1355 "line": 25 1542 "line": 26
1356 } 1543 }
1357 }, 1544 },
1358 { 1545 {
1359 "defaultMessage": "!!!Manage Team", 1546 "defaultMessage": "!!!Manage Team",
1360 "end": { 1547 "end": {
1361 "column": 3, 1548 "column": 3,
1362 "line": 32 1549 "line": 33
1363 }, 1550 },
1364 "file": "src/components/settings/navigation/SettingsNavigation.js", 1551 "file": "src/components/settings/navigation/SettingsNavigation.js",
1365 "id": "settings.navigation.team", 1552 "id": "settings.navigation.team",
1366 "start": { 1553 "start": {
1367 "column": 8, 1554 "column": 8,
1368 "line": 29 1555 "line": 30
1369 } 1556 }
1370 }, 1557 },
1371 { 1558 {
1372 "defaultMessage": "!!!Settings", 1559 "defaultMessage": "!!!Settings",
1373 "end": { 1560 "end": {
1374 "column": 3, 1561 "column": 3,
1375 "line": 36 1562 "line": 37
1376 }, 1563 },
1377 "file": "src/components/settings/navigation/SettingsNavigation.js", 1564 "file": "src/components/settings/navigation/SettingsNavigation.js",
1378 "id": "settings.navigation.settings", 1565 "id": "settings.navigation.settings",
1379 "start": { 1566 "start": {
1380 "column": 12, 1567 "column": 12,
1381 "line": 33 1568 "line": 34
1382 } 1569 }
1383 }, 1570 },
1384 { 1571 {
1385 "defaultMessage": "!!!Invite Friends", 1572 "defaultMessage": "!!!Invite Friends",
1386 "end": { 1573 "end": {
1387 "column": 3, 1574 "column": 3,
1388 "line": 40 1575 "line": 41
1389 }, 1576 },
1390 "file": "src/components/settings/navigation/SettingsNavigation.js", 1577 "file": "src/components/settings/navigation/SettingsNavigation.js",
1391 "id": "settings.navigation.inviteFriends", 1578 "id": "settings.navigation.inviteFriends",
1392 "start": { 1579 "start": {
1393 "column": 17, 1580 "column": 17,
1394 "line": 37 1581 "line": 38
1395 } 1582 }
1396 }, 1583 },
1397 { 1584 {
1398 "defaultMessage": "!!!Logout", 1585 "defaultMessage": "!!!Logout",
1399 "end": { 1586 "end": {
1400 "column": 3, 1587 "column": 3,
1401 "line": 44 1588 "line": 45
1402 }, 1589 },
1403 "file": "src/components/settings/navigation/SettingsNavigation.js", 1590 "file": "src/components/settings/navigation/SettingsNavigation.js",
1404 "id": "settings.navigation.logout", 1591 "id": "settings.navigation.logout",
1405 "start": { 1592 "start": {
1406 "column": 10, 1593 "column": 10,
1407 "line": 41 1594 "line": 42
1408 } 1595 }
1409 } 1596 }
1410 ], 1597 ],
@@ -1416,104 +1603,182 @@
1416 "defaultMessage": "!!!Available Services", 1603 "defaultMessage": "!!!Available Services",
1417 "end": { 1604 "end": {
1418 "column": 3, 1605 "column": 3,
1419 "line": 18 1606 "line": 23
1420 }, 1607 },
1421 "file": "src/components/settings/recipes/RecipesDashboard.js", 1608 "file": "src/components/settings/recipes/RecipesDashboard.js",
1422 "id": "settings.recipes.headline", 1609 "id": "settings.recipes.headline",
1423 "start": { 1610 "start": {
1424 "column": 12, 1611 "column": 12,
1425 "line": 15 1612 "line": 20
1426 } 1613 }
1427 }, 1614 },
1428 { 1615 {
1429 "defaultMessage": "!!!Search service", 1616 "defaultMessage": "!!!Search service",
1430 "end": { 1617 "end": {
1431 "column": 3, 1618 "column": 3,
1432 "line": 22 1619 "line": 27
1433 }, 1620 },
1434 "file": "src/components/settings/recipes/RecipesDashboard.js", 1621 "file": "src/components/settings/recipes/RecipesDashboard.js",
1435 "id": "settings.searchService", 1622 "id": "settings.searchService",
1436 "start": { 1623 "start": {
1437 "column": 17, 1624 "column": 17,
1438 "line": 19 1625 "line": 24
1439 } 1626 }
1440 }, 1627 },
1441 { 1628 {
1442 "defaultMessage": "!!!Most popular", 1629 "defaultMessage": "!!!Most popular",
1443 "end": { 1630 "end": {
1444 "column": 3, 1631 "column": 3,
1445 "line": 26 1632 "line": 31
1446 }, 1633 },
1447 "file": "src/components/settings/recipes/RecipesDashboard.js", 1634 "file": "src/components/settings/recipes/RecipesDashboard.js",
1448 "id": "settings.recipes.mostPopular", 1635 "id": "settings.recipes.mostPopular",
1449 "start": { 1636 "start": {
1450 "column": 22, 1637 "column": 22,
1451 "line": 23 1638 "line": 28
1452 } 1639 }
1453 }, 1640 },
1454 { 1641 {
1455 "defaultMessage": "!!!All services", 1642 "defaultMessage": "!!!All services",
1456 "end": { 1643 "end": {
1457 "column": 3, 1644 "column": 3,
1458 "line": 30 1645 "line": 35
1459 }, 1646 },
1460 "file": "src/components/settings/recipes/RecipesDashboard.js", 1647 "file": "src/components/settings/recipes/RecipesDashboard.js",
1461 "id": "settings.recipes.all", 1648 "id": "settings.recipes.all",
1462 "start": { 1649 "start": {
1463 "column": 14, 1650 "column": 14,
1464 "line": 27 1651 "line": 32
1465 } 1652 }
1466 }, 1653 },
1467 { 1654 {
1468 "defaultMessage": "!!!Development", 1655 "defaultMessage": "!!!Custom Services",
1469 "end": { 1656 "end": {
1470 "column": 3, 1657 "column": 3,
1471 "line": 34 1658 "line": 39
1472 }, 1659 },
1473 "file": "src/components/settings/recipes/RecipesDashboard.js", 1660 "file": "src/components/settings/recipes/RecipesDashboard.js",
1474 "id": "settings.recipes.dev", 1661 "id": "settings.recipes.custom",
1475 "start": { 1662 "start": {
1476 "column": 14, 1663 "column": 17,
1477 "line": 31 1664 "line": 36
1478 } 1665 }
1479 }, 1666 },
1480 { 1667 {
1481 "defaultMessage": "!!!Sorry, but no service matched your search term.", 1668 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1482 "end": { 1669 "end": {
1483 "column": 3, 1670 "column": 3,
1484 "line": 38 1671 "line": 43
1485 }, 1672 },
1486 "file": "src/components/settings/recipes/RecipesDashboard.js", 1673 "file": "src/components/settings/recipes/RecipesDashboard.js",
1487 "id": "settings.recipes.nothingFound", 1674 "id": "settings.recipes.nothingFound",
1488 "start": { 1675 "start": {
1489 "column": 16, 1676 "column": 16,
1490 "line": 35 1677 "line": 40
1491 } 1678 }
1492 }, 1679 },
1493 { 1680 {
1494 "defaultMessage": "!!!Service successfully added", 1681 "defaultMessage": "!!!Service successfully added",
1495 "end": { 1682 "end": {
1496 "column": 3, 1683 "column": 3,
1497 "line": 42 1684 "line": 47
1498 }, 1685 },
1499 "file": "src/components/settings/recipes/RecipesDashboard.js", 1686 "file": "src/components/settings/recipes/RecipesDashboard.js",
1500 "id": "settings.recipes.servicesSuccessfulAddedInfo", 1687 "id": "settings.recipes.servicesSuccessfulAddedInfo",
1501 "start": { 1688 "start": {
1502 "column": 31, 1689 "column": 31,
1503 "line": 39 1690 "line": 44
1504 } 1691 }
1505 }, 1692 },
1506 { 1693 {
1507 "defaultMessage": "!!!Missing a service?", 1694 "defaultMessage": "!!!Missing a service?",
1508 "end": { 1695 "end": {
1509 "column": 3, 1696 "column": 3,
1510 "line": 46 1697 "line": 51
1511 }, 1698 },
1512 "file": "src/components/settings/recipes/RecipesDashboard.js", 1699 "file": "src/components/settings/recipes/RecipesDashboard.js",
1513 "id": "settings.recipes.missingService", 1700 "id": "settings.recipes.missingService",
1514 "start": { 1701 "start": {
1515 "column": 18, 1702 "column": 18,
1516 "line": 43 1703 "line": 48
1704 }
1705 },
1706 {
1707 "defaultMessage": "!!!To add a custom service, copy the recipe folder into:",
1708 "end": {
1709 "column": 3,
1710 "line": 55
1711 },
1712 "file": "src/components/settings/recipes/RecipesDashboard.js",
1713 "id": "settings.recipes.customService.intro",
1714 "start": {
1715 "column": 21,
1716 "line": 52
1717 }
1718 },
1719 {
1720 "defaultMessage": "!!!Open directory",
1721 "end": {
1722 "column": 3,
1723 "line": 59
1724 },
1725 "file": "src/components/settings/recipes/RecipesDashboard.js",
1726 "id": "settings.recipes.customService.openFolder",
1727 "start": {
1728 "column": 14,
1729 "line": 56
1730 }
1731 },
1732 {
1733 "defaultMessage": "!!!Developer Documentation",
1734 "end": {
1735 "column": 3,
1736 "line": 63
1737 },
1738 "file": "src/components/settings/recipes/RecipesDashboard.js",
1739 "id": "settings.recipes.customService.openDevDocs",
1740 "start": {
1741 "column": 15,
1742 "line": 60
1743 }
1744 },
1745 {
1746 "defaultMessage": "!!!Custom Service Recipes",
1747 "end": {
1748 "column": 3,
1749 "line": 67
1750 },
1751 "file": "src/components/settings/recipes/RecipesDashboard.js",
1752 "id": "settings.recipes.customService.headline.customRecipes",
1753 "start": {
1754 "column": 25,
1755 "line": 64
1756 }
1757 },
1758 {
1759 "defaultMessage": "!!!Community Services",
1760 "end": {
1761 "column": 3,
1762 "line": 71
1763 },
1764 "file": "src/components/settings/recipes/RecipesDashboard.js",
1765 "id": "settings.recipes.customService.headline.communityRecipes",
1766 "start": {
1767 "column": 28,
1768 "line": 68
1769 }
1770 },
1771 {
1772 "defaultMessage": "!!!Your Development Service Recipes",
1773 "end": {
1774 "column": 3,
1775 "line": 75
1776 },
1777 "file": "src/components/settings/recipes/RecipesDashboard.js",
1778 "id": "settings.recipes.customService.headline.devRecipes",
1779 "start": {
1780 "column": 22,
1781 "line": 72
1517 } 1782 }
1518 } 1783 }
1519 ], 1784 ],
@@ -1525,286 +1790,286 @@
1525 "defaultMessage": "!!!Save service", 1790 "defaultMessage": "!!!Save service",
1526 "end": { 1791 "end": {
1527 "column": 3, 1792 "column": 3,
1528 "line": 25 1793 "line": 27
1529 }, 1794 },
1530 "file": "src/components/settings/services/EditServiceForm.js", 1795 "file": "src/components/settings/services/EditServiceForm.js",
1531 "id": "settings.service.form.saveButton", 1796 "id": "settings.service.form.saveButton",
1532 "start": { 1797 "start": {
1533 "column": 15, 1798 "column": 15,
1534 "line": 22 1799 "line": 24
1535 } 1800 }
1536 }, 1801 },
1537 { 1802 {
1538 "defaultMessage": "!!!Delete Service", 1803 "defaultMessage": "!!!Delete Service",
1539 "end": { 1804 "end": {
1540 "column": 3, 1805 "column": 3,
1541 "line": 29 1806 "line": 31
1542 }, 1807 },
1543 "file": "src/components/settings/services/EditServiceForm.js", 1808 "file": "src/components/settings/services/EditServiceForm.js",
1544 "id": "settings.service.form.deleteButton", 1809 "id": "settings.service.form.deleteButton",
1545 "start": { 1810 "start": {
1546 "column": 17, 1811 "column": 17,
1547 "line": 26 1812 "line": 28
1548 } 1813 }
1549 }, 1814 },
1550 { 1815 {
1551 "defaultMessage": "!!!Available services", 1816 "defaultMessage": "!!!Available services",
1552 "end": { 1817 "end": {
1553 "column": 3, 1818 "column": 3,
1554 "line": 33 1819 "line": 35
1555 }, 1820 },
1556 "file": "src/components/settings/services/EditServiceForm.js", 1821 "file": "src/components/settings/services/EditServiceForm.js",
1557 "id": "settings.service.form.availableServices", 1822 "id": "settings.service.form.availableServices",
1558 "start": { 1823 "start": {
1559 "column": 21, 1824 "column": 21,
1560 "line": 30 1825 "line": 32
1561 } 1826 }
1562 }, 1827 },
1563 { 1828 {
1564 "defaultMessage": "!!!Your services", 1829 "defaultMessage": "!!!Your services",
1565 "end": { 1830 "end": {
1566 "column": 3, 1831 "column": 3,
1567 "line": 37 1832 "line": 39
1568 }, 1833 },
1569 "file": "src/components/settings/services/EditServiceForm.js", 1834 "file": "src/components/settings/services/EditServiceForm.js",
1570 "id": "settings.service.form.yourServices", 1835 "id": "settings.service.form.yourServices",
1571 "start": { 1836 "start": {
1572 "column": 16, 1837 "column": 16,
1573 "line": 34 1838 "line": 36
1574 } 1839 }
1575 }, 1840 },
1576 { 1841 {
1577 "defaultMessage": "!!!Add {name}", 1842 "defaultMessage": "!!!Add {name}",
1578 "end": { 1843 "end": {
1579 "column": 3, 1844 "column": 3,
1580 "line": 41 1845 "line": 43
1581 }, 1846 },
1582 "file": "src/components/settings/services/EditServiceForm.js", 1847 "file": "src/components/settings/services/EditServiceForm.js",
1583 "id": "settings.service.form.addServiceHeadline", 1848 "id": "settings.service.form.addServiceHeadline",
1584 "start": { 1849 "start": {
1585 "column": 22, 1850 "column": 22,
1586 "line": 38 1851 "line": 40
1587 } 1852 }
1588 }, 1853 },
1589 { 1854 {
1590 "defaultMessage": "!!!Edit {name}", 1855 "defaultMessage": "!!!Edit {name}",
1591 "end": { 1856 "end": {
1592 "column": 3, 1857 "column": 3,
1593 "line": 45 1858 "line": 47
1594 }, 1859 },
1595 "file": "src/components/settings/services/EditServiceForm.js", 1860 "file": "src/components/settings/services/EditServiceForm.js",
1596 "id": "settings.service.form.editServiceHeadline", 1861 "id": "settings.service.form.editServiceHeadline",
1597 "start": { 1862 "start": {
1598 "column": 23, 1863 "column": 23,
1599 "line": 42 1864 "line": 44
1600 } 1865 }
1601 }, 1866 },
1602 { 1867 {
1603 "defaultMessage": "!!!Hosted", 1868 "defaultMessage": "!!!Hosted",
1604 "end": { 1869 "end": {
1605 "column": 3, 1870 "column": 3,
1606 "line": 49 1871 "line": 51
1607 }, 1872 },
1608 "file": "src/components/settings/services/EditServiceForm.js", 1873 "file": "src/components/settings/services/EditServiceForm.js",
1609 "id": "settings.service.form.tabHosted", 1874 "id": "settings.service.form.tabHosted",
1610 "start": { 1875 "start": {
1611 "column": 13, 1876 "column": 13,
1612 "line": 46 1877 "line": 48
1613 } 1878 }
1614 }, 1879 },
1615 { 1880 {
1616 "defaultMessage": "!!!Self hosted ⭐️", 1881 "defaultMessage": "!!!Self hosted ⭐️",
1617 "end": { 1882 "end": {
1618 "column": 3, 1883 "column": 3,
1619 "line": 53 1884 "line": 55
1620 }, 1885 },
1621 "file": "src/components/settings/services/EditServiceForm.js", 1886 "file": "src/components/settings/services/EditServiceForm.js",
1622 "id": "settings.service.form.tabOnPremise", 1887 "id": "settings.service.form.tabOnPremise",
1623 "start": { 1888 "start": {
1624 "column": 16, 1889 "column": 16,
1625 "line": 50 1890 "line": 52
1626 } 1891 }
1627 }, 1892 },
1628 { 1893 {
1629 "defaultMessage": "!!!Use the hosted {name} service.", 1894 "defaultMessage": "!!!Use the hosted {name} service.",
1630 "end": { 1895 "end": {
1631 "column": 3, 1896 "column": 3,
1632 "line": 57 1897 "line": 59
1633 }, 1898 },
1634 "file": "src/components/settings/services/EditServiceForm.js", 1899 "file": "src/components/settings/services/EditServiceForm.js",
1635 "id": "settings.service.form.useHostedService", 1900 "id": "settings.service.form.useHostedService",
1636 "start": { 1901 "start": {
1637 "column": 20, 1902 "column": 20,
1638 "line": 54 1903 "line": 56
1639 } 1904 }
1640 }, 1905 },
1641 { 1906 {
1642 "defaultMessage": "!!!Could not validate custom {name} server.", 1907 "defaultMessage": "!!!Could not validate custom {name} server.",
1643 "end": { 1908 "end": {
1644 "column": 3, 1909 "column": 3,
1645 "line": 61 1910 "line": 63
1646 }, 1911 },
1647 "file": "src/components/settings/services/EditServiceForm.js", 1912 "file": "src/components/settings/services/EditServiceForm.js",
1648 "id": "settings.service.form.customUrlValidationError", 1913 "id": "settings.service.form.customUrlValidationError",
1649 "start": { 1914 "start": {
1650 "column": 28, 1915 "column": 28,
1651 "line": 58 1916 "line": 60
1652 } 1917 }
1653 }, 1918 },
1654 { 1919 {
1655 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", 1920 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.",
1656 "end": { 1921 "end": {
1657 "column": 3, 1922 "column": 3,
1658 "line": 65 1923 "line": 67
1659 }, 1924 },
1660 "file": "src/components/settings/services/EditServiceForm.js", 1925 "file": "src/components/settings/services/EditServiceForm.js",
1661 "id": "settings.service.form.customUrlPremiumInfo", 1926 "id": "settings.service.form.customUrlPremiumInfo",
1662 "start": { 1927 "start": {
1663 "column": 24, 1928 "column": 24,
1664 "line": 62 1929 "line": 64
1665 } 1930 }
1666 }, 1931 },
1667 { 1932 {
1668 "defaultMessage": "!!!Upgrade your account", 1933 "defaultMessage": "!!!Upgrade your account",
1669 "end": { 1934 "end": {
1670 "column": 3, 1935 "column": 3,
1671 "line": 69 1936 "line": 71
1672 }, 1937 },
1673 "file": "src/components/settings/services/EditServiceForm.js", 1938 "file": "src/components/settings/services/EditServiceForm.js",
1674 "id": "settings.service.form.customUrlUpgradeAccount", 1939 "id": "settings.service.form.customUrlUpgradeAccount",
1675 "start": { 1940 "start": {
1676 "column": 27, 1941 "column": 27,
1677 "line": 66 1942 "line": 68
1678 } 1943 }
1679 }, 1944 },
1680 { 1945 {
1681 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 1946 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
1682 "end": { 1947 "end": {
1683 "column": 3, 1948 "column": 3,
1684 "line": 73 1949 "line": 75
1685 }, 1950 },
1686 "file": "src/components/settings/services/EditServiceForm.js", 1951 "file": "src/components/settings/services/EditServiceForm.js",
1687 "id": "settings.service.form.indirectMessageInfo", 1952 "id": "settings.service.form.indirectMessageInfo",
1688 "start": { 1953 "start": {
1689 "column": 23, 1954 "column": 23,
1690 "line": 70 1955 "line": 72
1691 } 1956 }
1692 }, 1957 },
1693 { 1958 {
1694 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 1959 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
1695 "end": { 1960 "end": {
1696 "column": 3, 1961 "column": 3,
1697 "line": 77 1962 "line": 79
1698 }, 1963 },
1699 "file": "src/components/settings/services/EditServiceForm.js", 1964 "file": "src/components/settings/services/EditServiceForm.js",
1700 "id": "settings.service.form.isMutedInfo", 1965 "id": "settings.service.form.isMutedInfo",
1701 "start": { 1966 "start": {
1702 "column": 15, 1967 "column": 15,
1703 "line": 74 1968 "line": 76
1704 } 1969 }
1705 }, 1970 },
1706 { 1971 {
1707 "defaultMessage": "!!!Notifications", 1972 "defaultMessage": "!!!Notifications",
1708 "end": { 1973 "end": {
1709 "column": 3, 1974 "column": 3,
1710 "line": 81 1975 "line": 83
1711 }, 1976 },
1712 "file": "src/components/settings/services/EditServiceForm.js", 1977 "file": "src/components/settings/services/EditServiceForm.js",
1713 "id": "settings.service.form.headlineNotifications", 1978 "id": "settings.service.form.headlineNotifications",
1714 "start": { 1979 "start": {
1715 "column": 25, 1980 "column": 25,
1716 "line": 78 1981 "line": 80
1717 } 1982 }
1718 }, 1983 },
1719 { 1984 {
1720 "defaultMessage": "!!!Unread message badges", 1985 "defaultMessage": "!!!Unread message badges",
1721 "end": { 1986 "end": {
1722 "column": 3, 1987 "column": 3,
1723 "line": 85 1988 "line": 87
1724 }, 1989 },
1725 "file": "src/components/settings/services/EditServiceForm.js", 1990 "file": "src/components/settings/services/EditServiceForm.js",
1726 "id": "settings.service.form.headlineBadges", 1991 "id": "settings.service.form.headlineBadges",
1727 "start": { 1992 "start": {
1728 "column": 18, 1993 "column": 18,
1729 "line": 82 1994 "line": 84
1730 } 1995 }
1731 }, 1996 },
1732 { 1997 {
1733 "defaultMessage": "!!!General", 1998 "defaultMessage": "!!!General",
1734 "end": { 1999 "end": {
1735 "column": 3, 2000 "column": 3,
1736 "line": 89 2001 "line": 91
1737 }, 2002 },
1738 "file": "src/components/settings/services/EditServiceForm.js", 2003 "file": "src/components/settings/services/EditServiceForm.js",
1739 "id": "settings.service.form.headlineGeneral", 2004 "id": "settings.service.form.headlineGeneral",
1740 "start": { 2005 "start": {
1741 "column": 19, 2006 "column": 19,
1742 "line": 86 2007 "line": 88
1743 } 2008 }
1744 }, 2009 },
1745 { 2010 {
1746 "defaultMessage": "!!!Delete", 2011 "defaultMessage": "!!!Delete",
1747 "end": { 2012 "end": {
1748 "column": 3, 2013 "column": 3,
1749 "line": 93 2014 "line": 95
1750 }, 2015 },
1751 "file": "src/components/settings/services/EditServiceForm.js", 2016 "file": "src/components/settings/services/EditServiceForm.js",
1752 "id": "settings.service.form.iconDelete", 2017 "id": "settings.service.form.iconDelete",
1753 "start": { 2018 "start": {
1754 "column": 14, 2019 "column": 14,
1755 "line": 90 2020 "line": 92
1756 } 2021 }
1757 }, 2022 },
1758 { 2023 {
1759 "defaultMessage": "!!!Drop your image, or click here", 2024 "defaultMessage": "!!!Drop your image, or click here",
1760 "end": { 2025 "end": {
1761 "column": 3, 2026 "column": 3,
1762 "line": 97 2027 "line": 99
1763 }, 2028 },
1764 "file": "src/components/settings/services/EditServiceForm.js", 2029 "file": "src/components/settings/services/EditServiceForm.js",
1765 "id": "settings.service.form.iconUpload", 2030 "id": "settings.service.form.iconUpload",
1766 "start": { 2031 "start": {
1767 "column": 14, 2032 "column": 14,
1768 "line": 94 2033 "line": 96
1769 } 2034 }
1770 }, 2035 },
1771 { 2036 {
1772 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 2037 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
1773 "end": { 2038 "end": {
1774 "column": 3, 2039 "column": 3,
1775 "line": 101 2040 "line": 103
1776 }, 2041 },
1777 "file": "src/components/settings/services/EditServiceForm.js", 2042 "file": "src/components/settings/services/EditServiceForm.js",
1778 "id": "settings.service.form.proxy.headline", 2043 "id": "settings.service.form.proxy.headline",
1779 "start": { 2044 "start": {
1780 "column": 17, 2045 "column": 17,
1781 "line": 98 2046 "line": 100
1782 } 2047 }
1783 }, 2048 },
1784 { 2049 {
1785 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", 2050 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.",
1786 "end": { 2051 "end": {
1787 "column": 3, 2052 "column": 3,
1788 "line": 105 2053 "line": 107
1789 }, 2054 },
1790 "file": "src/components/settings/services/EditServiceForm.js", 2055 "file": "src/components/settings/services/EditServiceForm.js",
1791 "id": "settings.service.form.proxy.restartInfo", 2056 "id": "settings.service.form.proxy.restartInfo",
1792 "start": { 2057 "start": {
1793 "column": 20, 2058 "column": 20,
1794 "line": 102 2059 "line": 104
1795 } 2060 }
1796 }, 2061 },
1797 { 2062 {
1798 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", 2063 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.",
1799 "end": { 2064 "end": {
1800 "column": 3, 2065 "column": 3,
1801 "line": 109 2066 "line": 111
1802 }, 2067 },
1803 "file": "src/components/settings/services/EditServiceForm.js", 2068 "file": "src/components/settings/services/EditServiceForm.js",
1804 "id": "settings.service.form.proxy.info", 2069 "id": "settings.service.form.proxy.info",
1805 "start": { 2070 "start": {
1806 "column": 13, 2071 "column": 13,
1807 "line": 106 2072 "line": 108
1808 } 2073 }
1809 } 2074 }
1810 ], 2075 ],
@@ -1917,117 +2182,117 @@
1917 "defaultMessage": "!!!Your services", 2182 "defaultMessage": "!!!Your services",
1918 "end": { 2183 "end": {
1919 "column": 3, 2184 "column": 3,
1920 "line": 17 2185 "line": 18
1921 }, 2186 },
1922 "file": "src/components/settings/services/ServicesDashboard.js", 2187 "file": "src/components/settings/services/ServicesDashboard.js",
1923 "id": "settings.services.headline", 2188 "id": "settings.services.headline",
1924 "start": { 2189 "start": {
1925 "column": 12, 2190 "column": 12,
1926 "line": 14 2191 "line": 15
1927 } 2192 }
1928 }, 2193 },
1929 { 2194 {
1930 "defaultMessage": "!!!Search service", 2195 "defaultMessage": "!!!Search service",
1931 "end": { 2196 "end": {
1932 "column": 3, 2197 "column": 3,
1933 "line": 21 2198 "line": 22
1934 }, 2199 },
1935 "file": "src/components/settings/services/ServicesDashboard.js", 2200 "file": "src/components/settings/services/ServicesDashboard.js",
1936 "id": "settings.searchService", 2201 "id": "settings.searchService",
1937 "start": { 2202 "start": {
1938 "column": 17, 2203 "column": 17,
1939 "line": 18 2204 "line": 19
1940 } 2205 }
1941 }, 2206 },
1942 { 2207 {
1943 "defaultMessage": "!!!You haven't added any services yet.", 2208 "defaultMessage": "!!!You haven't added any services yet.",
1944 "end": { 2209 "end": {
1945 "column": 3, 2210 "column": 3,
1946 "line": 25 2211 "line": 26
1947 }, 2212 },
1948 "file": "src/components/settings/services/ServicesDashboard.js", 2213 "file": "src/components/settings/services/ServicesDashboard.js",
1949 "id": "settings.services.noServicesAdded", 2214 "id": "settings.services.noServicesAdded",
1950 "start": { 2215 "start": {
1951 "column": 19, 2216 "column": 19,
1952 "line": 22 2217 "line": 23
1953 } 2218 }
1954 }, 2219 },
1955 { 2220 {
1956 "defaultMessage": "!!!Sorry, but no service matched your search term.", 2221 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1957 "end": { 2222 "end": {
1958 "column": 3, 2223 "column": 3,
1959 "line": 29 2224 "line": 30
1960 }, 2225 },
1961 "file": "src/components/settings/services/ServicesDashboard.js", 2226 "file": "src/components/settings/services/ServicesDashboard.js",
1962 "id": "settings.recipes.nothingFound", 2227 "id": "settings.recipes.nothingFound",
1963 "start": { 2228 "start": {
1964 "column": 18, 2229 "column": 18,
1965 "line": 26 2230 "line": 27
1966 } 2231 }
1967 }, 2232 },
1968 { 2233 {
1969 "defaultMessage": "!!!Discover services", 2234 "defaultMessage": "!!!Discover services",
1970 "end": { 2235 "end": {
1971 "column": 3, 2236 "column": 3,
1972 "line": 33 2237 "line": 34
1973 }, 2238 },
1974 "file": "src/components/settings/services/ServicesDashboard.js", 2239 "file": "src/components/settings/services/ServicesDashboard.js",
1975 "id": "settings.services.discoverServices", 2240 "id": "settings.services.discoverServices",
1976 "start": { 2241 "start": {
1977 "column": 20, 2242 "column": 20,
1978 "line": 30 2243 "line": 31
1979 } 2244 }
1980 }, 2245 },
1981 { 2246 {
1982 "defaultMessage": "!!!Could not load your services", 2247 "defaultMessage": "!!!Could not load your services",
1983 "end": { 2248 "end": {
1984 "column": 3, 2249 "column": 3,
1985 "line": 37 2250 "line": 38
1986 }, 2251 },
1987 "file": "src/components/settings/services/ServicesDashboard.js", 2252 "file": "src/components/settings/services/ServicesDashboard.js",
1988 "id": "settings.services.servicesRequestFailed", 2253 "id": "settings.services.servicesRequestFailed",
1989 "start": { 2254 "start": {
1990 "column": 25, 2255 "column": 25,
1991 "line": 34 2256 "line": 35
1992 } 2257 }
1993 }, 2258 },
1994 { 2259 {
1995 "defaultMessage": "!!!Try again", 2260 "defaultMessage": "!!!Try again",
1996 "end": { 2261 "end": {
1997 "column": 3, 2262 "column": 3,
1998 "line": 41 2263 "line": 42
1999 }, 2264 },
2000 "file": "src/components/settings/services/ServicesDashboard.js", 2265 "file": "src/components/settings/services/ServicesDashboard.js",
2001 "id": "settings.account.tryReloadServices", 2266 "id": "settings.account.tryReloadServices",
2002 "start": { 2267 "start": {
2003 "column": 21, 2268 "column": 21,
2004 "line": 38 2269 "line": 39
2005 } 2270 }
2006 }, 2271 },
2007 { 2272 {
2008 "defaultMessage": "!!!Your changes have been saved", 2273 "defaultMessage": "!!!Your changes have been saved",
2009 "end": { 2274 "end": {
2010 "column": 3, 2275 "column": 3,
2011 "line": 45 2276 "line": 46
2012 }, 2277 },
2013 "file": "src/components/settings/services/ServicesDashboard.js", 2278 "file": "src/components/settings/services/ServicesDashboard.js",
2014 "id": "settings.services.updatedInfo", 2279 "id": "settings.services.updatedInfo",
2015 "start": { 2280 "start": {
2016 "column": 15, 2281 "column": 15,
2017 "line": 42 2282 "line": 43
2018 } 2283 }
2019 }, 2284 },
2020 { 2285 {
2021 "defaultMessage": "!!!Service has been deleted", 2286 "defaultMessage": "!!!Service has been deleted",
2022 "end": { 2287 "end": {
2023 "column": 3, 2288 "column": 3,
2024 "line": 49 2289 "line": 50
2025 }, 2290 },
2026 "file": "src/components/settings/services/ServicesDashboard.js", 2291 "file": "src/components/settings/services/ServicesDashboard.js",
2027 "id": "settings.services.deletedInfo", 2292 "id": "settings.services.deletedInfo",
2028 "start": { 2293 "start": {
2029 "column": 15, 2294 "column": 15,
2030 "line": 46 2295 "line": 47
2031 } 2296 }
2032 } 2297 }
2033 ], 2298 ],
@@ -2659,6 +2924,159 @@
2659 { 2924 {
2660 "descriptors": [ 2925 "descriptors": [
2661 { 2926 {
2927 "defaultMessage": "!!!Your trial was successfully activated. Happy messaging!",
2928 "end": {
2929 "column": 3,
2930 "line": 14
2931 },
2932 "file": "src/components/TrialActivationInfoBar.js",
2933 "id": "infobar.trialActivated",
2934 "start": {
2935 "column": 11,
2936 "line": 11
2937 }
2938 }
2939 ],
2940 "path": "src/components/TrialActivationInfoBar.json"
2941 },
2942 {
2943 "descriptors": [
2944 {
2945 "defaultMessage": "!!!Add unlimited services",
2946 "end": {
2947 "column": 3,
2948 "line": 11
2949 },
2950 "file": "src/components/ui/FeatureList.js",
2951 "id": "pricing.features.unlimitedServices",
2952 "start": {
2953 "column": 21,
2954 "line": 8
2955 }
2956 },
2957 {
2958 "defaultMessage": "!!!Spellchecker support",
2959 "end": {
2960 "column": 3,
2961 "line": 15
2962 },
2963 "file": "src/components/ui/FeatureList.js",
2964 "id": "pricing.features.spellchecker",
2965 "start": {
2966 "column": 16,
2967 "line": 12
2968 }
2969 },
2970 {
2971 "defaultMessage": "!!!Workspaces",
2972 "end": {
2973 "column": 3,
2974 "line": 19
2975 },
2976 "file": "src/components/ui/FeatureList.js",
2977 "id": "pricing.features.workspaces",
2978 "start": {
2979 "column": 14,
2980 "line": 16
2981 }
2982 },
2983 {
2984 "defaultMessage": "!!!Add Custom Websites",
2985 "end": {
2986 "column": 3,
2987 "line": 23
2988 },
2989 "file": "src/components/ui/FeatureList.js",
2990 "id": "pricing.features.customWebsites",
2991 "start": {
2992 "column": 18,
2993 "line": 20
2994 }
2995 },
2996 {
2997 "defaultMessage": "!!!On-premise & other Hosted Services",
2998 "end": {
2999 "column": 3,
3000 "line": 27
3001 },
3002 "file": "src/components/ui/FeatureList.js",
3003 "id": "pricing.features.onPremise",
3004 "start": {
3005 "column": 13,
3006 "line": 24
3007 }
3008 },
3009 {
3010 "defaultMessage": "!!!Install 3rd party services",
3011 "end": {
3012 "column": 3,
3013 "line": 31
3014 },
3015 "file": "src/components/ui/FeatureList.js",
3016 "id": "pricing.features.thirdPartyServices",
3017 "start": {
3018 "column": 22,
3019 "line": 28
3020 }
3021 },
3022 {
3023 "defaultMessage": "!!!Service Proxies",
3024 "end": {
3025 "column": 3,
3026 "line": 35
3027 },
3028 "file": "src/components/ui/FeatureList.js",
3029 "id": "pricing.features.serviceProxies",
3030 "start": {
3031 "column": 18,
3032 "line": 32
3033 }
3034 },
3035 {
3036 "defaultMessage": "!!!Team Management",
3037 "end": {
3038 "column": 3,
3039 "line": 39
3040 },
3041 "file": "src/components/ui/FeatureList.js",
3042 "id": "pricing.features.teamManagement",
3043 "start": {
3044 "column": 18,
3045 "line": 36
3046 }
3047 },
3048 {
3049 "defaultMessage": "!!!No Waiting Screens",
3050 "end": {
3051 "column": 3,
3052 "line": 43
3053 },
3054 "file": "src/components/ui/FeatureList.js",
3055 "id": "pricing.features.appDelays",
3056 "start": {
3057 "column": 13,
3058 "line": 40
3059 }
3060 },
3061 {
3062 "defaultMessage": "!!!Forever ad-free",
3063 "end": {
3064 "column": 3,
3065 "line": 47
3066 },
3067 "file": "src/components/ui/FeatureList.js",
3068 "id": "pricing.features.adFree",
3069 "start": {
3070 "column": 10,
3071 "line": 44
3072 }
3073 }
3074 ],
3075 "path": "src/components/ui/FeatureList.json"
3076 },
3077 {
3078 "descriptors": [
3079 {
2662 "defaultMessage": "!!!Upgrade account", 3080 "defaultMessage": "!!!Upgrade account",
2663 "end": { 3081 "end": {
2664 "column": 3, 3082 "column": 3,
@@ -3230,39 +3648,65 @@
3230 "defaultMessage": "!!!Please purchase license to skip waiting", 3648 "defaultMessage": "!!!Please purchase license to skip waiting",
3231 "end": { 3649 "end": {
3232 "column": 3, 3650 "column": 3,
3233 "line": 18 3651 "line": 20
3234 }, 3652 },
3235 "file": "src/features/delayApp/Component.js", 3653 "file": "src/features/delayApp/Component.js",
3236 "id": "feature.delayApp.headline", 3654 "id": "feature.delayApp.headline",
3237 "start": { 3655 "start": {
3238 "column": 12, 3656 "column": 12,
3239 "line": 15 3657 "line": 17
3658 }
3659 },
3660 {
3661 "defaultMessage": "!!!Get the free Franz Professional 14 day trial and skip the line",
3662 "end": {
3663 "column": 3,
3664 "line": 24
3665 },
3666 "file": "src/features/delayApp/Component.js",
3667 "id": "feature.delayApp.trial.headline",
3668 "start": {
3669 "column": 17,
3670 "line": 21
3240 } 3671 }
3241 }, 3672 },
3242 { 3673 {
3243 "defaultMessage": "!!!Get a Franz Supporter License", 3674 "defaultMessage": "!!!Get a Franz Supporter License",
3244 "end": { 3675 "end": {
3245 "column": 3, 3676 "column": 3,
3246 "line": 22 3677 "line": 28
3247 }, 3678 },
3248 "file": "src/features/delayApp/Component.js", 3679 "file": "src/features/delayApp/Component.js",
3249 "id": "feature.delayApp.action", 3680 "id": "feature.delayApp.upgrade.action",
3250 "start": { 3681 "start": {
3251 "column": 10, 3682 "column": 10,
3252 "line": 19 3683 "line": 25
3684 }
3685 },
3686 {
3687 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
3688 "end": {
3689 "column": 3,
3690 "line": 32
3691 },
3692 "file": "src/features/delayApp/Component.js",
3693 "id": "feature.delayApp.trial.action",
3694 "start": {
3695 "column": 15,
3696 "line": 29
3253 } 3697 }
3254 }, 3698 },
3255 { 3699 {
3256 "defaultMessage": "!!!Franz will continue in {seconds} seconds.", 3700 "defaultMessage": "!!!Franz will continue in {seconds} seconds.",
3257 "end": { 3701 "end": {
3258 "column": 3, 3702 "column": 3,
3259 "line": 26 3703 "line": 36
3260 }, 3704 },
3261 "file": "src/features/delayApp/Component.js", 3705 "file": "src/features/delayApp/Component.js",
3262 "id": "feature.delayApp.text", 3706 "id": "feature.delayApp.text",
3263 "start": { 3707 "start": {
3264 "column": 8, 3708 "column": 8,
3265 "line": 23 3709 "line": 33
3266 } 3710 }
3267 } 3711 }
3268 ], 3712 ],
@@ -3271,6 +3715,55 @@
3271 { 3715 {
3272 "descriptors": [ 3716 "descriptors": [
3273 { 3717 {
3718 "defaultMessage": "!!!Changes in Franz {version}",
3719 "end": {
3720 "column": 3,
3721 "line": 23
3722 },
3723 "file": "src/features/serviceLimit/components/AnnouncementScreen.js",
3724 "id": "feature.announcements.changelog.headline",
3725 "start": {
3726 "column": 12,
3727 "line": 20
3728 }
3729 }
3730 ],
3731 "path": "src/features/serviceLimit/components/AnnouncementScreen.json"
3732 },
3733 {
3734 "descriptors": [
3735 {
3736 "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.",
3737 "end": {
3738 "column": 3,
3739 "line": 14
3740 },
3741 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3742 "id": "feature.serviceLimit.limitReached",
3743 "start": {
3744 "column": 16,
3745 "line": 11
3746 }
3747 },
3748 {
3749 "defaultMessage": "!!!Upgrade account",
3750 "end": {
3751 "column": 3,
3752 "line": 18
3753 },
3754 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3755 "id": "premiumFeature.button.upgradeAccount",
3756 "start": {
3757 "column": 10,
3758 "line": 15
3759 }
3760 }
3761 ],
3762 "path": "src/features/serviceLimit/components/LimitReachedInfobox.json"
3763 },
3764 {
3765 "descriptors": [
3766 {
3274 "defaultMessage": "!!!Franz is better together!", 3767 "defaultMessage": "!!!Franz is better together!",
3275 "end": { 3768 "end": {
3276 "column": 3, 3769 "column": 3,
@@ -3761,6 +4254,172 @@
3761 { 4254 {
3762 "descriptors": [ 4255 "descriptors": [
3763 { 4256 {
4257 "defaultMessage": "!!!Franz Professional Yearly",
4258 "end": {
4259 "column": 3,
4260 "line": 8
4261 },
4262 "file": "src/helpers/plan-helpers.js",
4263 "id": "pricing.plan.pro-yearly",
4264 "start": {
4265 "column": 22,
4266 "line": 5
4267 }
4268 },
4269 {
4270 "defaultMessage": "!!!Franz Professional Monthly",
4271 "end": {
4272 "column": 3,
4273 "line": 12
4274 },
4275 "file": "src/helpers/plan-helpers.js",
4276 "id": "pricing.plan.pro-monthly",
4277 "start": {
4278 "column": 23,
4279 "line": 9
4280 }
4281 },
4282 {
4283 "defaultMessage": "!!!Franz Personal Yearly",
4284 "end": {
4285 "column": 3,
4286 "line": 16
4287 },
4288 "file": "src/helpers/plan-helpers.js",
4289 "id": "pricing.plan.personal-yearly",
4290 "start": {
4291 "column": 27,
4292 "line": 13
4293 }
4294 },
4295 {
4296 "defaultMessage": "!!!Franz Personal Monthly",
4297 "end": {
4298 "column": 3,
4299 "line": 20
4300 },
4301 "file": "src/helpers/plan-helpers.js",
4302 "id": "pricing.plan.personal-monthly",
4303 "start": {
4304 "column": 28,
4305 "line": 17
4306 }
4307 },
4308 {
4309 "defaultMessage": "!!!Franz Free",
4310 "end": {
4311 "column": 3,
4312 "line": 24
4313 },
4314 "file": "src/helpers/plan-helpers.js",
4315 "id": "pricing.plan.free",
4316 "start": {
4317 "column": 16,
4318 "line": 21
4319 }
4320 },
4321 {
4322 "defaultMessage": "!!!Franz Premium",
4323 "end": {
4324 "column": 3,
4325 "line": 28
4326 },
4327 "file": "src/helpers/plan-helpers.js",
4328 "id": "pricing.plan.legacy",
4329 "start": {
4330 "column": 18,
4331 "line": 25
4332 }
4333 }
4334 ],
4335 "path": "src/helpers/plan-helpers.json"
4336 },
4337 {
4338 "descriptors": [
4339 {
4340 "defaultMessage": "!!!Franz Professional Yearly",
4341 "end": {
4342 "column": 3,
4343 "line": 8
4344 },
4345 "file": "src/helpers/pricing-helpers.js",
4346 "id": "pricing.plan.pro-yearly",
4347 "start": {
4348 "column": 22,
4349 "line": 5
4350 }
4351 },
4352 {
4353 "defaultMessage": "!!!Franz Professional Monthly",
4354 "end": {
4355 "column": 3,
4356 "line": 12
4357 },
4358 "file": "src/helpers/pricing-helpers.js",
4359 "id": "pricing.plan.pro-monthly",
4360 "start": {
4361 "column": 23,
4362 "line": 9
4363 }
4364 },
4365 {
4366 "defaultMessage": "!!!Franz Personal Yearly",
4367 "end": {
4368 "column": 3,
4369 "line": 16
4370 },
4371 "file": "src/helpers/pricing-helpers.js",
4372 "id": "pricing.plan.personal-yearly",
4373 "start": {
4374 "column": 27,
4375 "line": 13
4376 }
4377 },
4378 {
4379 "defaultMessage": "!!!Franz Personal Monthly",
4380 "end": {
4381 "column": 3,
4382 "line": 20
4383 },
4384 "file": "src/helpers/pricing-helpers.js",
4385 "id": "pricing.plan.personal-monthly",
4386 "start": {
4387 "column": 28,
4388 "line": 17
4389 }
4390 },
4391 {
4392 "defaultMessage": "!!!Franz Free",
4393 "end": {
4394 "column": 3,
4395 "line": 24
4396 },
4397 "file": "src/helpers/pricing-helpers.js",
4398 "id": "pricing.plan.free",
4399 "start": {
4400 "column": 16,
4401 "line": 21
4402 }
4403 },
4404 {
4405 "defaultMessage": "!!!Franz Premium",
4406 "end": {
4407 "column": 3,
4408 "line": 28
4409 },
4410 "file": "src/helpers/pricing-helpers.js",
4411 "id": "pricing.plan.legacy",
4412 "start": {
4413 "column": 18,
4414 "line": 25
4415 }
4416 }
4417 ],
4418 "path": "src/helpers/pricing-helpers.json"
4419 },
4420 {
4421 "descriptors": [
4422 {
3764 "defaultMessage": "!!!Field is required", 4423 "defaultMessage": "!!!Field is required",
3765 "end": { 4424 "end": {
3766 "column": 3, 4425 "column": 3,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 27987e5b7..cd1e39543 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",
@@ -106,10 +110,31 @@
106 "password.submit.label": "Submit", 110 "password.submit.label": "Submit",
107 "password.successInfo": "Please check your email", 111 "password.successInfo": "Please check your email",
108 "premiumFeature.button.upgradeAccount": "Upgrade account", 112 "premiumFeature.button.upgradeAccount": "Upgrade account",
109 "pricing.headline": "Support Franz", 113 "pricing.features.adFree": "Forever ad-free",
110 "pricing.link.skipPayment": "I don't want to support the development of Franz.", 114 "pricing.features.appDelays": "No Waiting Screens",
111 "pricing.submit.label": "I want to support the development of Franz", 115 "pricing.features.customWebsites": "Add Custom Websites",
112 "pricing.support.label": "Select your support plan", 116 "pricing.features.onPremise": "On-premise & other Hosted Services",
117 "pricing.features.serviceProxies": "Service Proxies",
118 "pricing.features.spellchecker": "Spellchecker support",
119 "pricing.features.teamManagement": "Team Management",
120 "pricing.features.thirdPartyServices": "Install 3rd party services",
121 "pricing.features.unlimitedServices": "Add unlimited services",
122 "pricing.features.workspaces": "Workspaces",
123 "pricing.plan.free": "Franz Free",
124 "pricing.plan.legacy": "Franz Premium",
125 "pricing.plan.personal-monthly": "Franz Personal Monthly",
126 "pricing.plan.personal-yearly": "Franz Personal Yearly",
127 "pricing.plan.pro-monthly": "Franz Professional Monthly",
128 "pricing.plan.pro-yearly": "Franz Professional Yearly",
129 "pricing.trial.cta.accept": "Yes, upgrade my account to Franz Professional",
130 "pricing.trial.cta.skip": "Continue to Franz",
131 "pricing.trial.error": "Sorry, we could not activate your trial!",
132 "pricing.trial.features.headline": "Franz Professional includes:",
133 "pricing.trial.headline": "Franz Professional",
134 "pricing.trial.subheadline": "Your personal welcome offer:",
135 "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days",
136 "pricing.trial.terms.headline": "No strings attached",
137 "pricing.trial.terms.noCreditCard": "No credit card required",
113 "service.crashHandler.action": "Reload {name}", 138 "service.crashHandler.action": "Reload {name}",
114 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", 139 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds",
115 "service.crashHandler.headline": "Oh no!", 140 "service.crashHandler.headline": "Oh no!",
@@ -121,6 +146,11 @@
121 "service.errorHandler.headline": "Oh no!", 146 "service.errorHandler.headline": "Oh no!",
122 "service.errorHandler.message": "Error", 147 "service.errorHandler.message": "Error",
123 "service.errorHandler.text": "{name} has failed to load.", 148 "service.errorHandler.text": "{name} has failed to load.",
149 "service.restrictedHandler.action": "Upgrade Account",
150 "service.restrictedHandler.customUrl.headline": "Franz Professional Plan required",
151 "service.restrictedHandler.customUrl.text": "Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
152 "service.restrictedHandler.serviceLimit.headline": "You have reached your service limit.",
153 "service.restrictedHandler.serviceLimit.text": "Please upgrade your account to use more than {count} services.",
124 "service.webviewLoader.loading": "Loading", 154 "service.webviewLoader.loading": "Loading",
125 "services.getStarted": "Get started", 155 "services.getStarted": "Get started",
126 "services.welcome": "Welcome to Franz", 156 "services.welcome": "Welcome to Franz",
@@ -142,9 +172,13 @@
142 "settings.account.invoiceDownload": "Download", 172 "settings.account.invoiceDownload": "Download",
143 "settings.account.manageSubscription.label": "Manage your subscription", 173 "settings.account.manageSubscription.label": "Manage your subscription",
144 "settings.account.successInfo": "Your changes have been saved", 174 "settings.account.successInfo": "Your changes have been saved",
175 "settings.account.trial": "Free Trial",
176 "settings.account.trialEndsIn": "Your free trial ends in {duration}.",
177 "settings.account.trialUpdateBillingInfo": "Please update your billing info to continue using {license} after your trial period.",
145 "settings.account.tryReloadServices": "Try again", 178 "settings.account.tryReloadServices": "Try again",
146 "settings.account.tryReloadUserInfoRequest": "Try again", 179 "settings.account.tryReloadUserInfoRequest": "Try again",
147 "settings.account.userInfoRequestFailed": "Could not load user information", 180 "settings.account.userInfoRequestFailed": "Could not load user information",
181 "settings.account.yourLicense": "Your license:",
148 "settings.app.buttonClearAllCache": "Clear cache", 182 "settings.app.buttonClearAllCache": "Clear cache",
149 "settings.app.buttonInstallUpdate": "Restart & install update", 183 "settings.app.buttonInstallUpdate": "Restart & install update",
150 "settings.app.buttonSearchForUpdate": "Check for updates", 184 "settings.app.buttonSearchForUpdate": "Check for updates",
@@ -185,7 +219,13 @@
185 "settings.navigation.yourServices": "Your services", 219 "settings.navigation.yourServices": "Your services",
186 "settings.navigation.yourWorkspaces": "Your workspaces", 220 "settings.navigation.yourWorkspaces": "Your workspaces",
187 "settings.recipes.all": "All services", 221 "settings.recipes.all": "All services",
188 "settings.recipes.dev": "Development", 222 "settings.recipes.custom": "Custom Services",
223 "settings.recipes.customService.headline.communityRecipes": "Community Services",
224 "settings.recipes.customService.headline.customRecipes": "Custom Service Recipes",
225 "settings.recipes.customService.headline.devRecipes": "Your Development Service Recipes",
226 "settings.recipes.customService.intro": "To add a custom service, copy the service recipe to:",
227 "settings.recipes.customService.openDevDocs": "Developer Documentation",
228 "settings.recipes.customService.openFolder": "Open folder",
189 "settings.recipes.headline": "Available services", 229 "settings.recipes.headline": "Available services",
190 "settings.recipes.missingService": "Missing a service?", 230 "settings.recipes.missingService": "Missing a service?",
191 "settings.recipes.mostPopular": "Most popular", 231 "settings.recipes.mostPopular": "Most popular",
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/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 190c5dff7..b71889155 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 26, 7 "line": 27,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 29, 11 "line": 30,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Reload services", 17 "defaultMessage": "!!!Reload services",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 30, 20 "line": 31,
21 "column": 24 21 "column": 24
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 33, 24 "line": 34,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Could not load services and user information", 30 "defaultMessage": "!!!Could not load services and user information",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 34, 33 "line": 35,
34 "column": 26 34 "column": 26
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 37, 37 "line": 38,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/i18n/messages/src/components/services/content/ServiceRestricted.json b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
new file mode 100644
index 000000000..c1984afe3
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "service.restrictedHandler.serviceLimit.headline",
4 "defaultMessage": "!!!You have reached your service limit.",
5 "file": "src/components/services/content/ServiceRestricted.js",
6 "start": {
7 "line": 11,
8 "column": 24
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "service.restrictedHandler.serviceLimit.text",
17 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
18 "file": "src/components/services/content/ServiceRestricted.js",
19 "start": {
20 "line": 15,
21 "column": 20
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 },
28 {
29 "id": "service.restrictedHandler.customUrl.headline",
30 "defaultMessage": "!!!Franz Professional Plan required",
31 "file": "src/components/services/content/ServiceRestricted.js",
32 "start": {
33 "line": 19,
34 "column": 21
35 },
36 "end": {
37 "line": 22,
38 "column": 3
39 }
40 },
41 {
42 "id": "service.restrictedHandler.customUrl.text",
43 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
44 "file": "src/components/services/content/ServiceRestricted.js",
45 "start": {
46 "line": 23,
47 "column": 17
48 },
49 "end": {
50 "line": 26,
51 "column": 3
52 }
53 },
54 {
55 "id": "service.restrictedHandler.action",
56 "defaultMessage": "!!!Upgrade Account",
57 "file": "src/components/services/content/ServiceRestricted.js",
58 "start": {
59 "line": 27,
60 "column": 10
61 },
62 "end": {
63 "line": 30,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json
index 884ab0c90..eb466c0ac 100644
--- a/src/i18n/messages/src/components/services/content/Services.json
+++ b/src/i18n/messages/src/components/services/content/Services.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Welcome to Franz", 4 "defaultMessage": "!!!Welcome to Franz",
5 "file": "src/components/services/content/Services.js", 5 "file": "src/components/services/content/Services.js",
6 "start": { 6 "start": {
7 "line": 11, 7 "line": 14,
8 "column": 11 8 "column": 11
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 14, 11 "line": 17,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Get started", 17 "defaultMessage": "!!!Get started",
18 "file": "src/components/services/content/Services.js", 18 "file": "src/components/services/content/Services.js",
19 "start": { 19 "start": {
20 "line": 15, 20 "line": 18,
21 "column": 14 21 "column": 14
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 18, 24 "line": 21,
25 "column": 3 25 "column": 3
26 } 26 }
27 } 27 }
diff --git a/src/i18n/messages/src/components/settings/account/AccountDashboard.json b/src/i18n/messages/src/components/settings/account/AccountDashboard.json
index 4969db910..1e21bad69 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,24 +17,24 @@
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, 20 "line": 22,
21 "column": 24 21 "column": 24
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 21, 24 "line": 25,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
28 { 28 {
29 "id": "settings.account.headlineUpgrade", 29 "id": "settings.account.headlineTrialUpgrade",
30 "defaultMessage": "!!!Upgrade your Account", 30 "defaultMessage": "!!!Get the free 14 day Franz Professional Trial",
31 "file": "src/components/settings/account/AccountDashboard.js", 31 "file": "src/components/settings/account/AccountDashboard.js",
32 "start": { 32 "start": {
33 "line": 22, 33 "line": 26,
34 "column": 19 34 "column": 19
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 25, 37 "line": 29,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!Danger Zone", 43 "defaultMessage": "!!Danger Zone",
44 "file": "src/components/settings/account/AccountDashboard.js", 44 "file": "src/components/settings/account/AccountDashboard.js",
45 "start": { 45 "start": {
46 "line": 26, 46 "line": 30,
47 "column": 22 47 "column": 22
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 29, 50 "line": 33,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Manage your subscription", 56 "defaultMessage": "!!!Manage your subscription",
57 "file": "src/components/settings/account/AccountDashboard.js", 57 "file": "src/components/settings/account/AccountDashboard.js",
58 "start": { 58 "start": {
59 "line": 30, 59 "line": 34,
60 "column": 33 60 "column": 33
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 33, 63 "line": 37,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
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 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..224a78c4c 100644
--- a/src/i18n/messages/src/components/subscription/SubscriptionForm.json
+++ b/src/i18n/messages/src/components/subscription/SubscriptionForm.json
@@ -1,7 +1,7 @@
1[ 1[
2 { 2 {
3 "id": "subscription.submit.label", 3 "id": "subscription.cta.activateTrial",
4 "defaultMessage": "!!!Support the development of Franz", 4 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional",
5 "file": "src/components/subscription/SubscriptionForm.js", 5 "file": "src/components/subscription/SubscriptionForm.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 14,
@@ -13,12 +13,12 @@
13 } 13 }
14 }, 14 },
15 { 15 {
16 "id": "subscription.paymentSessionError", 16 "id": "subscription.includedProFeatures",
17 "defaultMessage": "!!!Could not initialize payment form", 17 "defaultMessage": "!!!The Franz Professional Plan includes:",
18 "file": "src/components/subscription/SubscriptionForm.js", 18 "file": "src/components/subscription/SubscriptionForm.js",
19 "start": { 19 "start": {
20 "line": 18, 20 "line": 18,
21 "column": 23 21 "column": 20
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 21, 24 "line": 21,
@@ -26,12 +26,12 @@
26 } 26 }
27 }, 27 },
28 { 28 {
29 "id": "subscription.type.free", 29 "id": "pricing.trial.terms.headline",
30 "defaultMessage": "!!!free", 30 "defaultMessage": "!!!No strings attached",
31 "file": "src/components/subscription/SubscriptionForm.js", 31 "file": "src/components/subscription/SubscriptionForm.js",
32 "start": { 32 "start": {
33 "line": 22, 33 "line": 22,
34 "column": 12 34 "column": 29
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 25, 37 "line": 25,
@@ -39,12 +39,12 @@
39 } 39 }
40 }, 40 },
41 { 41 {
42 "id": "subscription.type.month", 42 "id": "pricing.trial.terms.noCreditCard",
43 "defaultMessage": "!!!month", 43 "defaultMessage": "!!!No credit card required",
44 "file": "src/components/subscription/SubscriptionForm.js", 44 "file": "src/components/subscription/SubscriptionForm.js",
45 "start": { 45 "start": {
46 "line": 26, 46 "line": 26,
47 "column": 15 47 "column": 16
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 29, 50 "line": 29,
@@ -52,133 +52,16 @@
52 } 52 }
53 }, 53 },
54 { 54 {
55 "id": "subscription.type.year", 55 "id": "pricing.trial.terms.automaticTrialEnd",
56 "defaultMessage": "!!!year", 56 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
57 "file": "src/components/subscription/SubscriptionForm.js", 57 "file": "src/components/subscription/SubscriptionForm.js",
58 "start": { 58 "start": {
59 "line": 30, 59 "line": 30,
60 "column": 14 60 "column": 21
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 33, 63 "line": 33,
64 "column": 3 64 "column": 3
65 } 65 }
66 },
67 {
68 "id": "subscription.includedFeatures",
69 "defaultMessage": "!!!The Franz Premium Supporter Account includes",
70 "file": "src/components/subscription/SubscriptionForm.js",
71 "start": {
72 "line": 34,
73 "column": 20
74 },
75 "end": {
76 "line": 37,
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
182 }
183 } 66 }
184] \ No newline at end of file 67] \ 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/helpers/plan-helpers.json b/src/i18n/messages/src/helpers/plan-helpers.json
new file mode 100644
index 000000000..de27dfcfd
--- /dev/null
+++ b/src/i18n/messages/src/helpers/plan-helpers.json
@@ -0,0 +1,80 @@
1[
2 {
3 "id": "pricing.plan.pro-yearly",
4 "defaultMessage": "!!!Franz Professional Yearly",
5 "file": "src/helpers/plan-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/plan-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/plan-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/plan-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/plan-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/plan-helpers.js",
71 "start": {
72 "line": 25,
73 "column": 18
74 },
75 "end": {
76 "line": 28,
77 "column": 3
78 }
79 }
80] \ No newline at end of file
diff --git a/src/i18n/messages/src/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/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 e7832088b..27334c9a2 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';
19 21
20import { DEFAULT_FEATURES_CONFIG } from '../config'; 22import { DEFAULT_FEATURES_CONFIG } from '../config';
21 23
@@ -75,5 +77,7 @@ export default class FeaturesStore extends Store {
75 shareFranz(this.stores, this.actions); 77 shareFranz(this.stores, this.actions);
76 announcements(this.stores, this.actions); 78 announcements(this.stores, this.actions);
77 settingsWS(this.stores, this.actions); 79 settingsWS(this.stores, this.actions);
80 serviceLimit(this.stores, this.actions);
81 communityRecipes(this.stores, this.actions);
78 } 82 }
79} 83}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index 109ac5cd7..e3b3fcf21 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;
@@ -678,6 +686,35 @@ export default class ServicesStore extends Store {
678 return serviceData; 686 return serviceData;
679 } 687 }
680 688
689 _restrictServiceAccess() {
690 const { features } = this.stores.features;
691 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
692
693 this.all.map((service, index) => {
694 if (userHasReachedServiceLimit) {
695 service.isServiceAccessRestricted = index >= serviceLimit;
696
697 if (service.isServiceAccessRestricted) {
698 service.restrictionType = RESTRICTION_TYPES.SERVICE_LIMIT;
699
700 debug('Restricting access to server due to service limit');
701 }
702 }
703
704 if (service.isUsingCustomUrl) {
705 service.isServiceAccessRestricted = !features.isCustomUrlIncludedInCurrentPlan;
706
707 if (service.isServiceAccessRestricted) {
708 service.restrictionType = RESTRICTION_TYPES.CUSTOM_URL;
709
710 debug('Restricting access to server due to custom url');
711 }
712 }
713
714 return service;
715 });
716 }
717
681 // Helper 718 // Helper
682 _initializeServiceRecipeInWebview(serviceId) { 719 _initializeServiceRecipeInWebview(serviceId) {
683 const service = this.one(serviceId); 720 const service = this.one(serviceId);
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index b5423af3b..e7516e6e6 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -2,12 +2,14 @@ import { observable, computed, action } from 'mobx';
2import moment from 'moment'; 2import moment from 'moment';
3import jwt from 'jsonwebtoken'; 3import jwt from 'jsonwebtoken';
4import localStorage from 'mobx-localstorage'; 4import localStorage from 'mobx-localstorage';
5import ms from 'ms';
5 6
6import { isDevMode } from '../environment'; 7import { isDevMode } from '../environment';
7import Store from './lib/Store'; 8import Store from './lib/Store';
8import Request from './lib/Request'; 9import Request from './lib/Request';
9import CachedRequest from './lib/CachedRequest'; 10import CachedRequest from './lib/CachedRequest';
10import { gaEvent } from '../lib/analytics'; 11import { gaEvent } from '../lib/analytics';
12import { sleep } from '../helpers/async-helpers';
11 13
12const debug = require('debug')('Franz:UserStore'); 14const debug = require('debug')('Franz:UserStore');
13 15
@@ -37,6 +39,8 @@ export default class UserStore extends Store {
37 39
38 @observable passwordRequest = new Request(this.api.user, 'password'); 40 @observable passwordRequest = new Request(this.api.user, 'password');
39 41
42 @observable activateTrialRequest = new Request(this.api.user, 'activateTrial');
43
40 @observable inviteRequest = new Request(this.api.user, 'invite'); 44 @observable inviteRequest = new Request(this.api.user, 'invite');
41 45
42 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 46 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
@@ -57,7 +61,9 @@ export default class UserStore extends Store {
57 61
58 @observable accountType; 62 @observable accountType;
59 63
60 @observable hasCompletedSignup = null; 64 @observable hasCompletedSignup = false;
65
66 @observable hasActivatedTrial = false;
61 67
62 @observable userData = {}; 68 @observable userData = {};
63 69
@@ -77,6 +83,7 @@ export default class UserStore extends Store {
77 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); 83 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
78 this.actions.user.logout.listen(this._logout.bind(this)); 84 this.actions.user.logout.listen(this._logout.bind(this));
79 this.actions.user.signup.listen(this._signup.bind(this)); 85 this.actions.user.signup.listen(this._signup.bind(this));
86 this.actions.user.activateTrial.listen(this._activateTrial.bind(this));
80 this.actions.user.invite.listen(this._invite.bind(this)); 87 this.actions.user.invite.listen(this._invite.bind(this));
81 this.actions.user.update.listen(this._update.bind(this)); 88 this.actions.user.update.listen(this._update.bind(this));
82 this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); 89 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
@@ -87,6 +94,7 @@ export default class UserStore extends Store {
87 this.registerReactions([ 94 this.registerReactions([
88 this._requireAuthenticatedUser, 95 this._requireAuthenticatedUser,
89 this._getUserData.bind(this), 96 this._getUserData.bind(this),
97 this._resetTrialActivationState.bind(this),
90 ]); 98 ]);
91 } 99 }
92 100
@@ -199,6 +207,26 @@ 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 console.log('hasActivatedTrial', this.hasActivatedTrial);
222
223 this.stores.features.featuresRequest.invalidate({ immediately: true });
224 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
225
226
227 gaEvent('User', 'activateTrial');
228 }
229
202 @action async _invite({ invites }) { 230 @action async _invite({ invites }) {
203 const data = invites.filter(invite => invite.email !== ''); 231 const data = invites.filter(invite => invite.email !== '');
204 232
@@ -318,6 +346,16 @@ export default class UserStore extends Store {
318 } 346 }
319 } 347 }
320 348
349 async _resetTrialActivationState() {
350 if (this.hasActivatedTrial) {
351 await sleep(ms('12s'));
352
353 console.log('resetting this.hasActivatedTrial', this.hasActivatedTrial);
354
355 this.hasActivatedTrial = false;
356 }
357 }
358
321 // Helpers 359 // Helpers
322 _parseToken(authToken) { 360 _parseToken(authToken) {
323 try { 361 try {
diff --git a/src/stores/index.js b/src/stores/index.js
index 1912418a2..c9ec77612 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';
15 17
16export default (api, actions, router) => { 18export default (api, actions, router) => {
17 const stores = {}; 19 const stores = {};
@@ -31,6 +33,8 @@ export default (api, actions, router) => {
31 globalError: new GlobalErrorStore(stores, api, actions), 33 globalError: new GlobalErrorStore(stores, api, actions),
32 workspaces: workspaceStore, 34 workspaces: workspaceStore,
33 announcements: announcementsStore, 35 announcements: announcementsStore,
36 serviceLimit: serviceLimitStore,
37 communityRecipes: communityRecipesStore,
34 }); 38 });
35 // Initialize all stores 39 // Initialize all stores
36 Object.keys(stores).forEach((name) => { 40 Object.keys(stores).forEach((name) => {
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%;