aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--packages/theme/src/themes/dark/index.ts4
-rw-r--r--src/actions/index.js2
-rw-r--r--src/components/auth/Pricing.js17
-rw-r--r--src/components/layout/AppLayout.js2
-rw-r--r--src/components/settings/account/AccountDashboard.js3
-rw-r--r--src/components/ui/FeatureItem.js1
-rw-r--r--src/components/ui/FeatureList.js73
-rw-r--r--src/config.js8
-rw-r--r--src/containers/auth/PricingScreen.js3
-rw-r--r--src/features/planSelection/actions.js13
-rw-r--r--src/features/planSelection/api.js26
-rw-r--r--src/features/planSelection/components/PlanItem.js186
-rw-r--r--src/features/planSelection/components/PlanSelection.js233
-rw-r--r--src/features/planSelection/containers/PlanSelectionScreen.js138
-rw-r--r--src/features/planSelection/index.js30
-rw-r--r--src/features/planSelection/store.js113
-rw-r--r--src/features/shareFranz/index.js3
-rw-r--r--src/helpers/plan-helpers.js8
-rw-r--r--src/i18n/locales/defaultMessages.json396
-rw-r--r--src/i18n/locales/en-US.json46
-rw-r--r--src/i18n/messages/src/components/auth/Pricing.json4
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json12
-rw-r--r--src/i18n/messages/src/components/ui/FeatureList.json105
-rw-r--r--src/i18n/messages/src/features/planSelection/components/PlanItem.json28
-rw-r--r--src/i18n/messages/src/features/planSelection/components/PlanSelection.json145
-rw-r--r--src/i18n/messages/src/features/planSelection/components/PlanTeaser.json28
-rw-r--r--src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json54
-rw-r--r--src/i18n/messages/src/helpers/plan-helpers.json8
-rw-r--r--src/stores/FeaturesStore.js2
-rw-r--r--src/stores/index.js2
30 files changed, 1589 insertions, 104 deletions
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index 67d0cfb71..9a66f3463 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -65,8 +65,8 @@ export const selectOptionItemHoverColor = selectColor;
65export const selectSearchColor = inputBackground; 65export const selectSearchColor = inputBackground;
66 66
67// Modal 67// Modal
68export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); 68export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.5).rgb().string();
69export const colorModalBackground = colorContentBackground; 69export const colorModalBackground = legacyStyles.darkThemeGrayDark;
70 70
71// Services 71// Services
72export const services = merge({}, defaultStyles.services, { 72export const services = merge({}, defaultStyles.services, {
diff --git a/src/actions/index.js b/src/actions/index.js
index 336344d76..1c033fb96 100644
--- a/src/actions/index.js
+++ b/src/actions/index.js
@@ -14,6 +14,7 @@ import requests from './requests';
14import announcements from '../features/announcements/actions'; 14import announcements from '../features/announcements/actions';
15import workspaces from '../features/workspaces/actions'; 15import workspaces from '../features/workspaces/actions';
16import todos from '../features/todos/actions'; 16import todos from '../features/todos/actions';
17import planSelection from '../features/planSelection/actions';
17 18
18const actions = Object.assign({}, { 19const actions = Object.assign({}, {
19 service, 20 service,
@@ -33,4 +34,5 @@ export default Object.assign(
33 { announcements }, 34 { announcements },
34 { workspaces }, 35 { workspaces },
35 { todos }, 36 { todos },
37 { planSelection },
36); 38);
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index cbeaaa5d9..40ce49814 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -18,7 +18,7 @@ const messages = defineMessages({
18 }, 18 },
19 personalOffer: { 19 personalOffer: {
20 id: 'pricing.trial.subheadline', 20 id: 'pricing.trial.subheadline',
21 defaultMessage: '!!!Your personal welcome offer:', 21 defaultMessage: '!!!Here\'s a special welcome for you:',
22 }, 22 },
23 noStringsAttachedHeadline: { 23 noStringsAttachedHeadline: {
24 id: 'pricing.trial.terms.headline', 24 id: 'pricing.trial.terms.headline',
@@ -38,7 +38,7 @@ const messages = defineMessages({
38 }, 38 },
39 ctaAccept: { 39 ctaAccept: {
40 id: 'pricing.trial.cta.accept', 40 id: 'pricing.trial.cta.accept',
41 defaultMessage: '!!!Yes, upgrade my account to Franz Professional', 41 defaultMessage: '!!!Start my 14-day Franz Professional Trial ',
42 }, 42 },
43 ctaSkip: { 43 ctaSkip: {
44 id: 'pricing.trial.cta.skip', 44 id: 'pricing.trial.cta.skip',
@@ -58,6 +58,7 @@ const styles = theme => ({
58 welcomeOffer: { 58 welcomeOffer: {
59 textAlign: 'center', 59 textAlign: 'center',
60 fontWeight: 'bold', 60 fontWeight: 'bold',
61 marginBottom: '6 !important',
61 }, 62 },
62 keyTerms: { 63 keyTerms: {
63 textAlign: 'center', 64 textAlign: 'center',
@@ -101,6 +102,7 @@ export default @observer @injectSheet(styles) class Signup extends Component {
101 isLoadingRequiredData: PropTypes.bool.isRequired, 102 isLoadingRequiredData: PropTypes.bool.isRequired,
102 isActivatingTrial: PropTypes.bool.isRequired, 103 isActivatingTrial: PropTypes.bool.isRequired,
103 trialActivationError: PropTypes.bool.isRequired, 104 trialActivationError: PropTypes.bool.isRequired,
105 canSkipTrial: PropTypes.bool.isRequired,
104 classes: PropTypes.object.isRequired, 106 classes: PropTypes.object.isRequired,
105 }; 107 };
106 108
@@ -114,6 +116,7 @@ export default @observer @injectSheet(styles) class Signup extends Component {
114 isLoadingRequiredData, 116 isLoadingRequiredData,
115 isActivatingTrial, 117 isActivatingTrial,
116 trialActivationError, 118 trialActivationError,
119 canSkipTrial,
117 classes, 120 classes,
118 } = this.props; 121 } = this.props;
119 const { intl } = this.context; 122 const { intl } = this.context;
@@ -138,7 +141,7 @@ export default @observer @injectSheet(styles) class Signup extends Component {
138 <br /> 141 <br />
139 </p> 142 </p>
140 <p> 143 <p>
141 Get the free 14 day Franz Professional trial and see your communication evolving. 144 For the next 14 days, we are going to give you the full Franz Professional experience so you can watch your communication evolve!
142 <br /> 145 <br />
143 </p> 146 </p>
144 <p> 147 <p>
@@ -167,9 +170,11 @@ export default @observer @injectSheet(styles) class Signup extends Component {
167 busy={isActivatingTrial} 170 busy={isActivatingTrial}
168 disabled={isLoadingRequiredData || isActivatingTrial} 171 disabled={isLoadingRequiredData || isActivatingTrial}
169 /> 172 />
170 <p className={classes.skipLink}> 173 {canSkipTrial && (
171 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a> 174 <p className={classes.skipLink}>
172 </p> 175 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a>
176 </p>
177 )}
173 </form> 178 </form>
174 </div> 179 </div>
175 <div className={classes.featureContainer}> 180 <div className={classes.featureContainer}>
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index 200777ae6..fe81b1911 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -19,6 +19,7 @@ import { workspaceStore } from '../../features/workspaces';
19import AppUpdateInfoBar from '../AppUpdateInfoBar'; 19import AppUpdateInfoBar from '../AppUpdateInfoBar';
20import TrialActivationInfoBar from '../TrialActivationInfoBar'; 20import TrialActivationInfoBar from '../TrialActivationInfoBar';
21import Todos from '../../features/todos/containers/TodosScreen'; 21import Todos from '../../features/todos/containers/TodosScreen';
22import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen';
22 23
23function createMarkup(HTMLString) { 24function createMarkup(HTMLString) {
24 return { __html: HTMLString }; 25 return { __html: HTMLString };
@@ -176,6 +177,7 @@ class AppLayout extends Component {
176 </div> 177 </div>
177 <Todos /> 178 <Todos />
178 </div> 179 </div>
180 <PlanSelection />
179 </div> 181 </div>
180 </ErrorBoundary> 182 </ErrorBoundary>
181 ); 183 );
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 08e86fda6..9a1b31d0f 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -217,7 +217,8 @@ class AccountDashboard extends Component {
217 {intl.formatMessage(messages.yourLicense)} 217 {intl.formatMessage(messages.yourLicense)}
218 </H2> 218 </H2>
219 <p> 219 <p>
220 {isPremiumOverrideUser ? 'Franz Premium' : planName} 220 Franz
221 {isPremiumOverrideUser ? 'Premium' : planName}
221 {user.team.isTrial && ( 222 {user.team.isTrial && (
222 <> 223 <>
223 {' – '} 224 {' – '}
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
index 7c482c4d4..4926df470 100644
--- a/src/components/ui/FeatureItem.js
+++ b/src/components/ui/FeatureItem.js
@@ -10,6 +10,7 @@ const styles = theme => ({
10 padding: [8, 0], 10 padding: [8, 0],
11 display: 'flex', 11 display: 'flex',
12 alignItems: 'center', 12 alignItems: 'center',
13 textAlign: 'left',
13 }, 14 },
14 featureIcon: { 15 featureIcon: {
15 fill: theme.brandSuccess, 16 fill: theme.brandSuccess,
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
index 62944ad75..732b40e40 100644
--- a/src/components/ui/FeatureList.js
+++ b/src/components/ui/FeatureList.js
@@ -3,12 +3,33 @@ import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4 4
5import { FeatureItem } from './FeatureItem'; 5import { FeatureItem } from './FeatureItem';
6import { PLANS } from '../../config';
6 7
7const messages = defineMessages({ 8const messages = defineMessages({
9 availableRecipes: {
10 id: 'pricing.features.recipes',
11 defaultMessage: '!!!Choose from more than 70 Services',
12 },
13 accountSync: {
14 id: 'pricing.features.accountSync',
15 defaultMessage: '!!!Account Synchronisation',
16 },
17 desktopNotifications: {
18 id: 'pricing.features.desktopNotifications',
19 defaultMessage: '!!!Desktop Notifications',
20 },
8 unlimitedServices: { 21 unlimitedServices: {
9 id: 'pricing.features.unlimitedServices', 22 id: 'pricing.features.unlimitedServices',
10 defaultMessage: '!!!Add unlimited services', 23 defaultMessage: '!!!Add unlimited services',
11 }, 24 },
25 upToThreeServices: {
26 id: 'pricing.features.upToThreeServices',
27 defaultMessage: '!!!Add up to 3 services',
28 },
29 upToSixServices: {
30 id: 'pricing.features.upToSixServices',
31 defaultMessage: '!!!Add up to 6 services',
32 },
12 spellchecker: { 33 spellchecker: {
13 id: 'pricing.features.spellchecker', 34 id: 'pricing.features.spellchecker',
14 defaultMessage: '!!!Spellchecker support', 35 defaultMessage: '!!!Spellchecker support',
@@ -51,6 +72,7 @@ export class FeatureList extends Component {
51 static propTypes = { 72 static propTypes = {
52 className: PropTypes.string, 73 className: PropTypes.string,
53 featureClassName: PropTypes.string, 74 featureClassName: PropTypes.string,
75 plan: PropTypes.oneOf(PLANS).isRequired,
54 }; 76 };
55 77
56 static defaultProps = { 78 static defaultProps = {
@@ -66,21 +88,52 @@ export class FeatureList extends Component {
66 const { 88 const {
67 className, 89 className,
68 featureClassName, 90 featureClassName,
91 plan,
69 } = this.props; 92 } = this.props;
70 const { intl } = this.context; 93 const { intl } = this.context;
71 94
95 const features = [];
96 if (plan === PLANS.FREE) {
97 features.push(
98 messages.upToThreeServices,
99 messages.availableRecipes,
100 messages.accountSync,
101 messages.desktopNotifications,
102 );
103 } else if (plan === PLANS.PERSONAL) {
104 features.push(
105 messages.upToSixServices,
106 messages.spellchecker,
107 messages.appDelays,
108 messages.adFree,
109 );
110 } else if (plan === PLANS.PRO) {
111 features.push(
112 messages.unlimitedServices,
113 messages.workspaces,
114 messages.customWebsites,
115 // messages.onPremise,
116 messages.thirdPartyServices,
117 // messages.serviceProxies,
118 );
119 } else {
120 features.push(
121 messages.unlimitedServices,
122 messages.spellchecker,
123 messages.workspaces,
124 messages.customWebsites,
125 messages.onPremise,
126 messages.thirdPartyServices,
127 messages.serviceProxies,
128 messages.teamManagement,
129 messages.appDelays,
130 messages.adFree,
131 );
132 }
133
72 return ( 134 return (
73 <ul className={className}> 135 <ul className={className}>
74 <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} className={featureClassName} /> 136 {features.map(feature => <FeatureItem name={intl.formatMessage(feature)} 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> 137 </ul>
85 ); 138 );
86 } 139 }
diff --git a/src/config.js b/src/config.js
index 78a92d948..11e6cb91f 100644
--- a/src/config.js
+++ b/src/config.js
@@ -91,10 +91,10 @@ export const ALLOWED_PROTOCOLS = [
91]; 91];
92 92
93export const PLANS = { 93export const PLANS = {
94 PERSONAL: 'PERSONAL', 94 PERSONAL: 'personal',
95 PRO: 'PRO', 95 PRO: 'pro',
96 LEGACY: 'LEGACY', 96 LEGACY: 'legacy',
97 FREE: 'FREE', 97 FREE: 'free',
98}; 98};
99 99
100export const PLANS_MAPPING = { 100export const PLANS_MAPPING = {
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js
index af1651931..8e0ded16a 100644
--- a/src/containers/auth/PricingScreen.js
+++ b/src/containers/auth/PricingScreen.js
@@ -38,7 +38,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
38 } = this.props; 38 } = this.props;
39 39
40 const { getUserInfoRequest, activateTrialRequest } = stores.user; 40 const { getUserInfoRequest, activateTrialRequest } = stores.user;
41 const { featuresRequest } = stores.features; 41 const { featuresRequest, features } = stores.features;
42 42
43 return ( 43 return (
44 <Pricing 44 <Pricing
@@ -46,6 +46,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
46 isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)} 46 isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)}
47 isActivatingTrial={activateTrialRequest.isExecuting} 47 isActivatingTrial={activateTrialRequest.isExecuting}
48 trialActivationError={activateTrialRequest.isError} 48 trialActivationError={activateTrialRequest.isError}
49 canSkipTrial={features.canSkipTrial}
49 error={error} 50 error={error}
50 /> 51 />
51 ); 52 );
diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js
new file mode 100644
index 000000000..21aa38ace
--- /dev/null
+++ b/src/features/planSelection/actions.js
@@ -0,0 +1,13 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const planSelectionActions = createActionsFromDefinitions({
5 upgradeAccount: {
6 planId: PropTypes.string.isRequired,
7 onCloseWindow: PropTypes.func.isRequired,
8 },
9 downgradeAccount: {},
10 hideOverlay: {},
11}, PropTypes.checkPropTypes);
12
13export default planSelectionActions;
diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js
new file mode 100644
index 000000000..734643f10
--- /dev/null
+++ b/src/features/planSelection/api.js
@@ -0,0 +1,26 @@
1import { sendAuthRequest } from '../../api/utils/auth';
2import { API, API_VERSION } from '../../environment';
3import Request from '../../stores/lib/Request';
4
5const debug = require('debug')('Franz:feature:planSelection:api');
6
7export const planSelectionApi = {
8 downgrade: async () => {
9 const url = `${API}/${API_VERSION}/payment/downgrade`;
10 const options = {
11 method: 'PUT',
12 };
13 debug('downgrade UPDATE', url, options);
14 const result = await sendAuthRequest(url, options);
15 debug('downgrade RESULT', result);
16 if (!result.ok) throw result;
17
18 return result.ok;
19 },
20};
21
22export const downgradeUserRequest = new Request(planSelectionApi, 'downgrade');
23
24export const resetApiRequests = () => {
25 downgradeUserRequest.reset();
26};
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js
new file mode 100644
index 000000000..a49cd40d3
--- /dev/null
+++ b/src/features/planSelection/components/PlanItem.js
@@ -0,0 +1,186 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7import color from 'color';
8
9import { H2 } from '@meetfranz/ui';
10
11import { Button } from '@meetfranz/forms';
12import { mdiArrowRight } from '@mdi/js';
13// import { FeatureList } from '../ui/FeatureList';
14// import { PLANS, PAYMENT_INTERVAL } from '../../config';
15// import { i18nPlanName, i18nIntervalName } from '../../helpers/plan-helpers';
16// import { PLAN_INTERVAL_CONFIG_TYPE } from './types';
17
18const messages = defineMessages({
19 perMonth: {
20 id: 'subscription.interval.perMonth',
21 defaultMessage: '!!!per month',
22 },
23 perMonthPerUser: {
24 id: 'subscription.interval.perMonthPerUser',
25 defaultMessage: '!!!per month & user',
26 },
27});
28
29const styles = theme => ({
30 root: {
31 display: 'flex',
32 flexDirection: 'column',
33 borderRadius: theme.borderRadius,
34 flex: 1,
35 color: theme.styleTypes.primary.accent,
36 overflow: 'hidden',
37 textAlign: 'center',
38
39 '& h2': {
40 textAlign: 'center',
41 marginBottom: 20,
42 fontSize: 30,
43 color: theme.styleTypes.primary.contrast,
44 // fontWeight: 'bold',
45 },
46 },
47 currency: {
48 fontSize: 35,
49 },
50 priceWrapper: {
51 height: 50,
52 },
53 price: {
54 fontSize: 50,
55
56 '& sup': {
57 fontSize: 20,
58 verticalAlign: 20,
59 },
60 },
61 interval: {
62 // paddingBottom: 40,
63 },
64 text: {
65 marginBottom: 'auto',
66 },
67 cta: {
68 background: theme.styleTypes.primary.accent,
69 color: theme.styleTypes.primary.contrast,
70 margin: [40, 'auto', 0, 'auto'],
71
72 // '&:active': {
73 // opacity: 0.7,
74 // },
75 },
76 divider: {
77 width: 40,
78 border: 0,
79 borderTop: [1, 'solid', theme.styleTypes.primary.contrast],
80 margin: [30, 'auto'],
81 },
82 header: {
83 padding: 20,
84 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
85 color: theme.styleTypes.primary.contrast,
86 },
87 content: {
88 padding: 20,
89 // border: [1, 'solid', 'red'],
90 background: '#EFEFEF',
91 },
92 simpleCTA: {
93 background: 'none',
94 color: theme.styleTypes.primary.accent,
95
96 '& svg': {
97 fill: theme.styleTypes.primary.accent,
98 },
99 },
100});
101
102
103export default @observer @injectSheet(styles) class PlanItem extends Component {
104 static propTypes = {
105 name: PropTypes.string.isRequired,
106 text: PropTypes.string.isRequired,
107 price: PropTypes.number.isRequired,
108 currency: PropTypes.string.isRequired,
109 upgrade: PropTypes.func.isRequired,
110 ctaLabel: PropTypes.string.isRequired,
111 simpleCTA: PropTypes.bool,
112 perUser: PropTypes.bool,
113 classes: PropTypes.object.isRequired,
114 children: PropTypes.element,
115 };
116
117 static defaultProps = {
118 simpleCTA: false,
119 perUser: false,
120 children: null,
121 }
122
123 static contextTypes = {
124 intl: intlShape,
125 };
126
127 render() {
128 const {
129 name,
130 text,
131 price,
132 currency,
133 classes,
134 upgrade,
135 ctaLabel,
136 simpleCTA,
137 perUser,
138 children,
139 } = this.props;
140 const { intl } = this.context;
141
142 const priceParts = `${price}`.split('.');
143 // const intervalName = i18nIntervalName(PAYMENT_INTERVAL.MONTHLY, intl);
144
145 return (
146 <div className={classes.root}>
147 <div className={classes.header}>
148 <H2 className={classes.planName}>{name}</H2>
149 <p className={classes.text}>
150 {text}
151 </p>
152 <hr className={classes.divider} />
153 <p className={classes.priceWrapper}>
154 <span className={classes.currency}>{currency}</span>
155 <span className={classes.price}>
156 {priceParts[0]}
157 <sup>{priceParts[1]}</sup>
158 </span>
159 </p>
160 <p className={classes.interval}>
161 {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)}
162 </p>
163 </div>
164
165 <div className={classes.content}>
166 {children}
167
168 <Button
169 className={classnames({
170 [classes.cta]: true,
171 [classes.simpleCTA]: simpleCTA,
172 })}
173 icon={simpleCTA ? mdiArrowRight : null}
174 label={(
175 <>
176 {ctaLabel}
177 </>
178 )}
179 onClick={upgrade}
180 />
181 </div>
182
183 </div>
184 );
185 }
186}
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
new file mode 100644
index 000000000..84d2d9e89
--- /dev/null
+++ b/src/features/planSelection/components/PlanSelection.js
@@ -0,0 +1,233 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl';
6import { H1, H2, Icon } from '@meetfranz/ui';
7import color from 'color';
8
9import { mdiRocket } from '@mdi/js';
10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { PLANS } from '../../../config';
13import { FeatureList } from '../../../components/ui/FeatureList';
14
15const messages = defineMessages({
16 welcome: {
17 id: 'feature.planSelection.fullscreen.welcome',
18 defaultMessage: '!!!Welcome back, {name}',
19 },
20 subheadline: {
21 id: 'feature.planSelection.fullscreen.subheadline',
22 defaultMessage: '!!!It\'s time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.',
23 },
24 textFree: {
25 id: 'feature.planSelection.free.text',
26 defaultMessage: '!!!Basic functionality',
27 },
28 textPersonal: {
29 id: 'feature.planSelection.personal.text',
30 defaultMessage: '!!!More services, no waiting - ideal for personal use.',
31 },
32 textProfessional: {
33 id: 'feature.planSelection.pro.text',
34 defaultMessage: '!!!Unlimited services and professional features for you - and your team.',
35 },
36 ctaStayOnFree: {
37 id: 'feature.planSelection.cta.stayOnFree',
38 defaultMessage: '!!!Stay on Free',
39 },
40 ctaDowngradeFree: {
41 id: 'feature.planSelection.cta.ctaDowngradeFree',
42 defaultMessage: '!!!Downgrade to Free',
43 },
44 actionTrial: {
45 id: 'feature.planSelection.cta.trial',
46 defaultMessage: '!!!Start my free 14-days Trial',
47 },
48 shortActionPersonal: {
49 id: 'feature.planSelection.cta.upgradePersonal',
50 defaultMessage: '!!!Choose Personal',
51 },
52 shortActionPro: {
53 id: 'feature.planSelection.cta.upgradePro',
54 defaultMessage: '!!!Choose Professional',
55 },
56 fullFeatureList: {
57 id: 'feature.planSelection.fullFeatureList',
58 defaultMessage: '!!!Complete comparison of all plans',
59 },
60});
61
62const styles = theme => ({
63 root: {
64 background: theme.colorModalOverlayBackground,
65 width: '100%',
66 height: '100%',
67 position: 'absolute',
68 top: 0,
69 left: 0,
70 display: 'flex',
71 justifyContent: 'center',
72 alignItems: 'center',
73 zIndex: 999999,
74 },
75 container: {
76 width: '80%',
77 height: 'auto',
78 background: theme.styleTypes.primary.accent,
79 padding: 40,
80 borderRadius: theme.borderRadius,
81 maxWidth: 1000,
82
83 '& h1, & h2': {
84 textAlign: 'center',
85 },
86 },
87 plans: {
88 display: 'flex',
89 margin: [40, 0, 0],
90 height: 'auto',
91
92 '& > div': {
93 margin: [0, 15],
94 flex: 1,
95 height: 'auto',
96 background: theme.styleTypes.primary.contrast,
97 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
98 },
99 },
100 bigIcon: {
101 background: theme.styleTypes.danger.accent,
102 width: 120,
103 height: 120,
104 display: 'flex',
105 alignItems: 'center',
106 borderRadius: '100%',
107 justifyContent: 'center',
108 margin: [-100, 'auto', 20],
109
110 '& svg': {
111 width: '80px !important',
112 },
113 },
114 headline: {
115 fontSize: 40,
116 },
117 subheadline: {
118 maxWidth: 660,
119 fontSize: 22,
120 lineHeight: 1.1,
121 margin: [0, 'auto'],
122 },
123 featureList: {
124 '& li': {
125 borderBottom: [1, 'solid', '#CECECE'],
126 },
127 },
128 fullFeatureList: {
129 marginTop: 40,
130 textAlign: 'center',
131 display: 'block',
132 color: `${theme.styleTypes.primary.contrast} !important`,
133 },
134});
135
136@injectSheet(styles) @observer
137class PlanSelection extends Component {
138 static propTypes = {
139 classes: PropTypes.object.isRequired,
140 firstname: PropTypes.string.isRequired,
141 plans: PropTypes.object.isRequired,
142 currency: PropTypes.string.isRequired,
143 subscriptionExpired: PropTypes.bool.isRequired,
144 upgradeAccount: PropTypes.func.isRequired,
145 stayOnFree: PropTypes.func.isRequired,
146 hadSubscription: PropTypes.bool.isRequired,
147 };
148
149 static contextTypes = {
150 intl: intlShape,
151 };
152
153 render() {
154 const {
155 classes,
156 firstname,
157 plans,
158 currency,
159 subscriptionExpired,
160 upgradeAccount,
161 stayOnFree,
162 hadSubscription,
163 } = this.props;
164
165 const { intl } = this.context;
166
167 return (
168 <div
169 className={classes.root}
170 >
171 <div className={classes.container}>
172 <div className={classes.bigIcon}>
173 <Icon icon={mdiRocket} />
174 </div>
175 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
176 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
177 <div className={classes.plans}>
178 <PlanItem
179 name={i18nPlanName(PLANS.FREE, intl)}
180 text={intl.formatMessage(messages.textFree)}
181 price={0}
182 currency={currency}
183 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
184 upgrade={() => stayOnFree()}
185 simpleCTA
186 >
187 <FeatureList
188 plan={PLANS.FREE}
189 className={classes.featureList}
190 />
191 </PlanItem>
192 <PlanItem
193 name={i18nPlanName(plans.personal.yearly.id, intl)}
194 text={intl.formatMessage(messages.textPersonal)}
195 price={plans.personal.yearly.price}
196 currency={currency}
197 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
198 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
199 >
200 <FeatureList
201 plan={PLANS.PERSONAL}
202 className={classes.featureList}
203 />
204 </PlanItem>
205 <PlanItem
206 name={i18nPlanName(plans.pro.yearly.id, intl)}
207 text={intl.formatMessage(messages.textProfessional)}
208 price={plans.pro.yearly.price}
209 currency={currency}
210 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
211 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
212 perUser
213 >
214 <FeatureList
215 plan={PLANS.PRO}
216 className={classes.featureList}
217 />
218 </PlanItem>
219 </div>
220 <a
221 href="https://meetfranz.com/pricing"
222 target="_blank"
223 className={classes.fullFeatureList}
224 >
225 {intl.formatMessage(messages.fullFeatureList)}
226 </a>
227 </div>
228 </div>
229 );
230 }
231}
232
233export default PlanSelection;
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js
new file mode 100644
index 000000000..b0d9b5ab5
--- /dev/null
+++ b/src/features/planSelection/containers/PlanSelectionScreen.js
@@ -0,0 +1,138 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4import { remote } from 'electron';
5import { defineMessages, intlShape } from 'react-intl';
6
7import FeaturesStore from '../../../stores/FeaturesStore';
8import UserStore from '../../../stores/UserStore';
9import PlanSelection from '../components/PlanSelection';
10import ErrorBoundary from '../../../components/util/ErrorBoundary';
11import { planSelectionStore } from '..';
12
13const { dialog, app } = remote;
14
15const messages = defineMessages({
16 dialogTitle: {
17 id: 'feature.planSelection.fullscreen.dialog.title',
18 defaultMessage: '!!!Downgrade your Franz Plan',
19 },
20 dialogMessage: {
21 id: 'feature.planSelection.fullscreen.dialog.message',
22 defaultMessage: '!!!You\'re about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.',
23 },
24 dialogCTADowngrade: {
25 id: 'feature.planSelection.fullscreen.dialog.cta.downgrade',
26 defaultMessage: '!!!Downgrade to Free',
27 },
28 dialogCTAUpgrade: {
29 id: 'feature.planSelection.fullscreen.dialog.cta.upgrade',
30 defaultMessage: '!!!Choose Personal',
31 },
32});
33
34@inject('stores', 'actions') @observer
35class PlanSelectionScreen extends Component {
36 static contextTypes = {
37 intl: intlShape,
38 };
39
40 upgradeAccount(planId) {
41 const { user, features } = this.props.stores;
42 const { upgradeAccount, hideOverlay } = this.props.actions.planSelection;
43
44 upgradeAccount({
45 planId,
46 onCloseWindow: () => {
47 hideOverlay();
48 user.getUserInfoRequest.invalidate({ immediately: true });
49 features.featuresRequest.invalidate({ immediately: true });
50 },
51 });
52 }
53
54 render() {
55 if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) {
56 return null;
57 }
58
59 const { intl } = this.context;
60
61 const { user, features } = this.props.stores;
62 const { plans, currency } = features.features.pricingConfig;
63 const { activateTrial } = this.props.actions.user;
64 const { upgradeAccount, downgradeAccount, hideOverlay } = this.props.actions.planSelection;
65
66 // const planConfig = [{
67 // id: 'free',
68 // price: 0,
69 // }, {
70 // id: plans.personal.yearly.id,
71 // price: plans.personal.yearly.price,
72 // }, {
73 // id: plans.pro.yearly.id,
74 // price: plans.pro.yearly.price,
75 // }];
76
77 return (
78 <ErrorBoundary>
79 <PlanSelection
80 firstname={user.data.firstname}
81 plans={plans}
82 currency={currency}
83 upgradeAccount={(planId) => {
84 if (user.data.hadSubscription) {
85 this.upgradeAccount(planId);
86 } else {
87 activateTrial({
88 planId,
89 });
90 }
91 }}
92 stayOnFree={() => {
93 const selection = dialog.showMessageBoxSync(app.mainWindow, {
94 type: 'question',
95 message: intl.formatMessage(messages.dialogTitle),
96 detail: intl.formatMessage(messages.dialogMessage, {
97 currency,
98 price: plans.personal.yearly.price,
99 }),
100 buttons: [
101 intl.formatMessage(messages.dialogCTADowngrade),
102 intl.formatMessage(messages.dialogCTAUpgrade),
103 ],
104 });
105
106 if (selection === 0) {
107 downgradeAccount();
108 hideOverlay();
109 } else {
110 upgradeAccount(plans.personal.yearly.id);
111 }
112 }}
113 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded}
114 hadSubscription={user.data.hadSubscription}
115 />
116 </ErrorBoundary>
117 );
118 }
119}
120
121export default PlanSelectionScreen;
122
123PlanSelectionScreen.wrappedComponent.propTypes = {
124 stores: PropTypes.shape({
125 features: PropTypes.instanceOf(FeaturesStore).isRequired,
126 user: PropTypes.instanceOf(UserStore).isRequired,
127 }).isRequired,
128 actions: PropTypes.shape({
129 planSelection: PropTypes.shape({
130 upgradeAccount: PropTypes.func.isRequired,
131 downgradeAccount: PropTypes.func.isRequired,
132 hideOverlay: PropTypes.func.isRequired,
133 }),
134 user: PropTypes.shape({
135 activateTrial: PropTypes.func.isRequired,
136 }),
137 }).isRequired,
138};
diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js
new file mode 100644
index 000000000..81189207a
--- /dev/null
+++ b/src/features/planSelection/index.js
@@ -0,0 +1,30 @@
1import { reaction } from 'mobx';
2import PlanSelectionStore from './store';
3
4const debug = require('debug')('Franz:feature:planSelection');
5
6export const GA_CATEGORY_PLAN_SELECTION = 'planSelection';
7
8export const planSelectionStore = new PlanSelectionStore();
9
10export default function initPlanSelection(stores, actions) {
11 stores.planSelection = planSelectionStore;
12 const { features } = stores;
13
14 // Toggle planSelection feature
15 reaction(
16 () => features.features.isPlanSelectionEnabled,
17 (isEnabled) => {
18 if (isEnabled) {
19 debug('Initializing `planSelection` feature');
20 planSelectionStore.start(stores, actions);
21 } else if (planSelectionStore.isFeatureActive) {
22 debug('Disabling `planSelection` feature');
23 planSelectionStore.stop();
24 }
25 },
26 {
27 fireImmediately: true,
28 },
29 );
30}
diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js
new file mode 100644
index 000000000..50e46dfb3
--- /dev/null
+++ b/src/features/planSelection/store.js
@@ -0,0 +1,113 @@
1import {
2 action,
3 observable,
4 computed,
5} from 'mobx';
6import { remote } from 'electron';
7
8import { planSelectionActions } from './actions';
9import { FeatureStore } from '../utils/FeatureStore';
10// import { createReactions } from '../../stores/lib/Reaction';
11import { createActionBindings } from '../utils/ActionBinding';
12import { downgradeUserRequest } from './api';
13
14const debug = require('debug')('Franz:feature:planSelection:store');
15
16const { BrowserWindow } = remote;
17
18export default class PlanSelectionStore extends FeatureStore {
19 @observable isFeatureEnabled = false;
20
21 @observable isFeatureActive = false;
22
23 @observable hideOverlay = false;
24
25 @computed get showPlanSelectionOverlay() {
26 const { team } = this.stores.user;
27 if (team && !this.hideOverlay) {
28 return team.state === 'expired' && !team.userHasDowngraded;
29 }
30
31 return false;
32 }
33
34 // ========== PUBLIC API ========= //
35
36 @action start(stores, actions, api) {
37 debug('PlanSelectionStore::start');
38 this.stores = stores;
39 this.actions = actions;
40 this.api = api;
41
42 // ACTIONS
43
44 this._registerActions(createActionBindings([
45 [planSelectionActions.upgradeAccount, this._upgradeAccount],
46 [planSelectionActions.downgradeAccount, this._downgradeAccount],
47 [planSelectionActions.hideOverlay, this._hideOverlay],
48 ]));
49
50 // REACTIONS
51
52 // this._allReactions = createReactions([
53 // this._setFeatureEnabledReaction,
54 // this._updateTodosConfig,
55 // this._firstLaunchReaction,
56 // this._routeCheckReaction,
57 // ]);
58
59 // this._registerReactions(this._allReactions);
60
61 this.isFeatureActive = true;
62 }
63
64 @action stop() {
65 super.stop();
66 debug('PlanSelectionStore::stop');
67 this.reset();
68 this.isFeatureActive = false;
69 }
70
71 // ========== PRIVATE METHODS ========= //
72
73 // Actions
74
75 @action _upgradeAccount = ({ planId, onCloseWindow = () => null }) => {
76 let hostedPageURL = this.stores.features.features.subscribeURL;
77
78 const parsedUrl = new URL(hostedPageURL);
79 const params = new URLSearchParams(parsedUrl.search.slice(1));
80
81 params.set('plan', planId);
82
83 hostedPageURL = this.stores.user.getAuthURL(`${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`);
84
85 const win = new BrowserWindow({
86 parent: remote.getCurrentWindow(),
87 modal: true,
88 title: '🔒 Upgrade Your Franz Account',
89 width: 800,
90 height: window.innerHeight - 100,
91 maxWidth: 800,
92 minWidth: 600,
93 webPreferences: {
94 nodeIntegration: true,
95 webviewTag: true,
96 },
97 });
98 win.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPageURL)}`);
99
100 win.on('closed', () => {
101 onCloseWindow();
102 });
103 };
104
105 @action _downgradeAccount = () => {
106 console.log('downgrade to free', downgradeUserRequest);
107 downgradeUserRequest.execute();
108 }
109
110 @action _hideOverlay = () => {
111 this.hideOverlay = true;
112 }
113}
diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js
index 87deacef4..a39d7a6e6 100644
--- a/src/features/shareFranz/index.js
+++ b/src/features/shareFranz/index.js
@@ -3,6 +3,7 @@ import ms from 'ms';
3 3
4import { state as delayAppState } from '../delayApp'; 4import { state as delayAppState } from '../delayApp';
5import { gaEvent, gaPage } from '../../lib/analytics'; 5import { gaEvent, gaPage } from '../../lib/analytics';
6import { planSelectionStore } from '../planSelection';
6 7
7export { default as Component } from './Component'; 8export { default as Component } from './Component';
8 9
@@ -35,7 +36,7 @@ export default function initialize(stores) {
35 () => stores.user.isLoggedIn, 36 () => stores.user.isLoggedIn,
36 () => { 37 () => {
37 setTimeout(() => { 38 setTimeout(() => {
38 if (stores.settings.stats.appStarts % 50 === 0) { 39 if (stores.settings.stats.appStarts % 50 === 0 && !planSelectionStore.showPlanSelectionOverlay) {
39 if (delayAppState.isDelayAppScreenVisible) { 40 if (delayAppState.isDelayAppScreenVisible) {
40 debug('Delaying share modal by 5 minutes'); 41 debug('Delaying share modal by 5 minutes');
41 setTimeout(() => showModal(), ms('5m')); 42 setTimeout(() => showModal(), ms('5m'));
diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js
index e0f1fd89a..ee22e4471 100644
--- a/src/helpers/plan-helpers.js
+++ b/src/helpers/plan-helpers.js
@@ -4,19 +4,19 @@ import { PLANS_MAPPING, PLANS } from '../config';
4const messages = defineMessages({ 4const messages = defineMessages({
5 [PLANS.PRO]: { 5 [PLANS.PRO]: {
6 id: 'pricing.plan.pro', 6 id: 'pricing.plan.pro',
7 defaultMessage: '!!!Franz Professional', 7 defaultMessage: '!!!Professional',
8 }, 8 },
9 [PLANS.PERSONAL]: { 9 [PLANS.PERSONAL]: {
10 id: 'pricing.plan.personal', 10 id: 'pricing.plan.personal',
11 defaultMessage: '!!!Franz Personal', 11 defaultMessage: '!!!Personal',
12 }, 12 },
13 [PLANS.FREE]: { 13 [PLANS.FREE]: {
14 id: 'pricing.plan.free', 14 id: 'pricing.plan.free',
15 defaultMessage: '!!!Franz Free', 15 defaultMessage: '!!!Free',
16 }, 16 },
17 [PLANS.LEGACY]: { 17 [PLANS.LEGACY]: {
18 id: 'pricing.plan.legacy', 18 id: 'pricing.plan.legacy',
19 defaultMessage: '!!!Franz Premium', 19 defaultMessage: '!!!Premium',
20 }, 20 },
21}); 21});
22 22
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 703f800f9..210ea001a 100644
--- a/src/i18n/locales/defaultMessages.json
+++ b/src/i18n/locales/defaultMessages.json
@@ -430,7 +430,7 @@
430 } 430 }
431 }, 431 },
432 { 432 {
433 "defaultMessage": "!!!Your personal welcome offer:", 433 "defaultMessage": "!!!Here's a special welcome for you:",
434 "end": { 434 "end": {
435 "column": 3, 435 "column": 3,
436 "line": 22 436 "line": 22
@@ -495,7 +495,7 @@
495 } 495 }
496 }, 496 },
497 { 497 {
498 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", 498 "defaultMessage": "!!!Start my 14-day Franz Professional Trial",
499 "end": { 499 "end": {
500 "column": 3, 500 "column": 3,
501 "line": 42 501 "line": 42
@@ -721,39 +721,39 @@
721 "defaultMessage": "!!!Your services have been updated.", 721 "defaultMessage": "!!!Your services have been updated.",
722 "end": { 722 "end": {
723 "column": 3, 723 "column": 3,
724 "line": 31 724 "line": 32
725 }, 725 },
726 "file": "src/components/layout/AppLayout.js", 726 "file": "src/components/layout/AppLayout.js",
727 "id": "infobar.servicesUpdated", 727 "id": "infobar.servicesUpdated",
728 "start": { 728 "start": {
729 "column": 19, 729 "column": 19,
730 "line": 28 730 "line": 29
731 } 731 }
732 }, 732 },
733 { 733 {
734 "defaultMessage": "!!!Reload services", 734 "defaultMessage": "!!!Reload services",
735 "end": { 735 "end": {
736 "column": 3, 736 "column": 3,
737 "line": 35 737 "line": 36
738 }, 738 },
739 "file": "src/components/layout/AppLayout.js", 739 "file": "src/components/layout/AppLayout.js",
740 "id": "infobar.buttonReloadServices", 740 "id": "infobar.buttonReloadServices",
741 "start": { 741 "start": {
742 "column": 24, 742 "column": 24,
743 "line": 32 743 "line": 33
744 } 744 }
745 }, 745 },
746 { 746 {
747 "defaultMessage": "!!!Could not load services and user information", 747 "defaultMessage": "!!!Could not load services and user information",
748 "end": { 748 "end": {
749 "column": 3, 749 "column": 3,
750 "line": 39 750 "line": 40
751 }, 751 },
752 "file": "src/components/layout/AppLayout.js", 752 "file": "src/components/layout/AppLayout.js",
753 "id": "infobar.requiredRequestsFailed", 753 "id": "infobar.requiredRequestsFailed",
754 "start": { 754 "start": {
755 "column": 26, 755 "column": 26,
756 "line": 36 756 "line": 37
757 } 757 }
758 } 758 }
759 ], 759 ],
@@ -3017,133 +3017,198 @@
3017 { 3017 {
3018 "descriptors": [ 3018 "descriptors": [
3019 { 3019 {
3020 "defaultMessage": "!!!Choose from more than 70 Services",
3021 "end": {
3022 "column": 3,
3023 "line": 12
3024 },
3025 "file": "src/components/ui/FeatureList.js",
3026 "id": "pricing.features.recipes",
3027 "start": {
3028 "column": 20,
3029 "line": 9
3030 }
3031 },
3032 {
3033 "defaultMessage": "!!!Account Synchronisation",
3034 "end": {
3035 "column": 3,
3036 "line": 16
3037 },
3038 "file": "src/components/ui/FeatureList.js",
3039 "id": "pricing.features.accountSync",
3040 "start": {
3041 "column": 15,
3042 "line": 13
3043 }
3044 },
3045 {
3046 "defaultMessage": "!!!Desktop Notifications",
3047 "end": {
3048 "column": 3,
3049 "line": 20
3050 },
3051 "file": "src/components/ui/FeatureList.js",
3052 "id": "pricing.features.desktopNotifications",
3053 "start": {
3054 "column": 24,
3055 "line": 17
3056 }
3057 },
3058 {
3020 "defaultMessage": "!!!Add unlimited services", 3059 "defaultMessage": "!!!Add unlimited services",
3021 "end": { 3060 "end": {
3022 "column": 3, 3061 "column": 3,
3023 "line": 11 3062 "line": 24
3024 }, 3063 },
3025 "file": "src/components/ui/FeatureList.js", 3064 "file": "src/components/ui/FeatureList.js",
3026 "id": "pricing.features.unlimitedServices", 3065 "id": "pricing.features.unlimitedServices",
3027 "start": { 3066 "start": {
3028 "column": 21, 3067 "column": 21,
3029 "line": 8 3068 "line": 21
3069 }
3070 },
3071 {
3072 "defaultMessage": "!!!Add up to 3 services",
3073 "end": {
3074 "column": 3,
3075 "line": 28
3076 },
3077 "file": "src/components/ui/FeatureList.js",
3078 "id": "pricing.features.upToThreeServices",
3079 "start": {
3080 "column": 21,
3081 "line": 25
3082 }
3083 },
3084 {
3085 "defaultMessage": "!!!Add up to 6 services",
3086 "end": {
3087 "column": 3,
3088 "line": 32
3089 },
3090 "file": "src/components/ui/FeatureList.js",
3091 "id": "pricing.features.upToSixServices",
3092 "start": {
3093 "column": 19,
3094 "line": 29
3030 } 3095 }
3031 }, 3096 },
3032 { 3097 {
3033 "defaultMessage": "!!!Spellchecker support", 3098 "defaultMessage": "!!!Spellchecker support",
3034 "end": { 3099 "end": {
3035 "column": 3, 3100 "column": 3,
3036 "line": 15 3101 "line": 36
3037 }, 3102 },
3038 "file": "src/components/ui/FeatureList.js", 3103 "file": "src/components/ui/FeatureList.js",
3039 "id": "pricing.features.spellchecker", 3104 "id": "pricing.features.spellchecker",
3040 "start": { 3105 "start": {
3041 "column": 16, 3106 "column": 16,
3042 "line": 12 3107 "line": 33
3043 } 3108 }
3044 }, 3109 },
3045 { 3110 {
3046 "defaultMessage": "!!!Workspaces", 3111 "defaultMessage": "!!!Workspaces",
3047 "end": { 3112 "end": {
3048 "column": 3, 3113 "column": 3,
3049 "line": 19 3114 "line": 40
3050 }, 3115 },
3051 "file": "src/components/ui/FeatureList.js", 3116 "file": "src/components/ui/FeatureList.js",
3052 "id": "pricing.features.workspaces", 3117 "id": "pricing.features.workspaces",
3053 "start": { 3118 "start": {
3054 "column": 14, 3119 "column": 14,
3055 "line": 16 3120 "line": 37
3056 } 3121 }
3057 }, 3122 },
3058 { 3123 {
3059 "defaultMessage": "!!!Add Custom Websites", 3124 "defaultMessage": "!!!Add Custom Websites",
3060 "end": { 3125 "end": {
3061 "column": 3, 3126 "column": 3,
3062 "line": 23 3127 "line": 44
3063 }, 3128 },
3064 "file": "src/components/ui/FeatureList.js", 3129 "file": "src/components/ui/FeatureList.js",
3065 "id": "pricing.features.customWebsites", 3130 "id": "pricing.features.customWebsites",
3066 "start": { 3131 "start": {
3067 "column": 18, 3132 "column": 18,
3068 "line": 20 3133 "line": 41
3069 } 3134 }
3070 }, 3135 },
3071 { 3136 {
3072 "defaultMessage": "!!!On-premise & other Hosted Services", 3137 "defaultMessage": "!!!On-premise & other Hosted Services",
3073 "end": { 3138 "end": {
3074 "column": 3, 3139 "column": 3,
3075 "line": 27 3140 "line": 48
3076 }, 3141 },
3077 "file": "src/components/ui/FeatureList.js", 3142 "file": "src/components/ui/FeatureList.js",
3078 "id": "pricing.features.onPremise", 3143 "id": "pricing.features.onPremise",
3079 "start": { 3144 "start": {
3080 "column": 13, 3145 "column": 13,
3081 "line": 24 3146 "line": 45
3082 } 3147 }
3083 }, 3148 },
3084 { 3149 {
3085 "defaultMessage": "!!!Install 3rd party services", 3150 "defaultMessage": "!!!Install 3rd party services",
3086 "end": { 3151 "end": {
3087 "column": 3, 3152 "column": 3,
3088 "line": 31 3153 "line": 52
3089 }, 3154 },
3090 "file": "src/components/ui/FeatureList.js", 3155 "file": "src/components/ui/FeatureList.js",
3091 "id": "pricing.features.thirdPartyServices", 3156 "id": "pricing.features.thirdPartyServices",
3092 "start": { 3157 "start": {
3093 "column": 22, 3158 "column": 22,
3094 "line": 28 3159 "line": 49
3095 } 3160 }
3096 }, 3161 },
3097 { 3162 {
3098 "defaultMessage": "!!!Service Proxies", 3163 "defaultMessage": "!!!Service Proxies",
3099 "end": { 3164 "end": {
3100 "column": 3, 3165 "column": 3,
3101 "line": 35 3166 "line": 56
3102 }, 3167 },
3103 "file": "src/components/ui/FeatureList.js", 3168 "file": "src/components/ui/FeatureList.js",
3104 "id": "pricing.features.serviceProxies", 3169 "id": "pricing.features.serviceProxies",
3105 "start": { 3170 "start": {
3106 "column": 18, 3171 "column": 18,
3107 "line": 32 3172 "line": 53
3108 } 3173 }
3109 }, 3174 },
3110 { 3175 {
3111 "defaultMessage": "!!!Team Management", 3176 "defaultMessage": "!!!Team Management",
3112 "end": { 3177 "end": {
3113 "column": 3, 3178 "column": 3,
3114 "line": 39 3179 "line": 60
3115 }, 3180 },
3116 "file": "src/components/ui/FeatureList.js", 3181 "file": "src/components/ui/FeatureList.js",
3117 "id": "pricing.features.teamManagement", 3182 "id": "pricing.features.teamManagement",
3118 "start": { 3183 "start": {
3119 "column": 18, 3184 "column": 18,
3120 "line": 36 3185 "line": 57
3121 } 3186 }
3122 }, 3187 },
3123 { 3188 {
3124 "defaultMessage": "!!!No Waiting Screens", 3189 "defaultMessage": "!!!No Waiting Screens",
3125 "end": { 3190 "end": {
3126 "column": 3, 3191 "column": 3,
3127 "line": 43 3192 "line": 64
3128 }, 3193 },
3129 "file": "src/components/ui/FeatureList.js", 3194 "file": "src/components/ui/FeatureList.js",
3130 "id": "pricing.features.appDelays", 3195 "id": "pricing.features.appDelays",
3131 "start": { 3196 "start": {
3132 "column": 13, 3197 "column": 13,
3133 "line": 40 3198 "line": 61
3134 } 3199 }
3135 }, 3200 },
3136 { 3201 {
3137 "defaultMessage": "!!!Forever ad-free", 3202 "defaultMessage": "!!!Forever ad-free",
3138 "end": { 3203 "end": {
3139 "column": 3, 3204 "column": 3,
3140 "line": 47 3205 "line": 68
3141 }, 3206 },
3142 "file": "src/components/ui/FeatureList.js", 3207 "file": "src/components/ui/FeatureList.js",
3143 "id": "pricing.features.adFree", 3208 "id": "pricing.features.adFree",
3144 "start": { 3209 "start": {
3145 "column": 10, 3210 "column": 10,
3146 "line": 44 3211 "line": 65
3147 } 3212 }
3148 } 3213 }
3149 ], 3214 ],
@@ -3834,6 +3899,273 @@
3834 { 3899 {
3835 "descriptors": [ 3900 "descriptors": [
3836 { 3901 {
3902 "defaultMessage": "!!!per month",
3903 "end": {
3904 "column": 3,
3905 "line": 22
3906 },
3907 "file": "src/features/planSelection/components/PlanItem.js",
3908 "id": "subscription.interval.perMonth",
3909 "start": {
3910 "column": 12,
3911 "line": 19
3912 }
3913 },
3914 {
3915 "defaultMessage": "!!!per month & user",
3916 "end": {
3917 "column": 3,
3918 "line": 26
3919 },
3920 "file": "src/features/planSelection/components/PlanItem.js",
3921 "id": "subscription.interval.perMonthPerUser",
3922 "start": {
3923 "column": 19,
3924 "line": 23
3925 }
3926 }
3927 ],
3928 "path": "src/features/planSelection/components/PlanItem.json"
3929 },
3930 {
3931 "descriptors": [
3932 {
3933 "defaultMessage": "!!!Welcome back, {name}",
3934 "end": {
3935 "column": 3,
3936 "line": 19
3937 },
3938 "file": "src/features/planSelection/components/PlanSelection.js",
3939 "id": "feature.planSelection.fullscreen.welcome",
3940 "start": {
3941 "column": 11,
3942 "line": 16
3943 }
3944 },
3945 {
3946 "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.",
3947 "end": {
3948 "column": 3,
3949 "line": 23
3950 },
3951 "file": "src/features/planSelection/components/PlanSelection.js",
3952 "id": "feature.planSelection.fullscreen.subheadline",
3953 "start": {
3954 "column": 15,
3955 "line": 20
3956 }
3957 },
3958 {
3959 "defaultMessage": "!!!Basic functionality",
3960 "end": {
3961 "column": 3,
3962 "line": 27
3963 },
3964 "file": "src/features/planSelection/components/PlanSelection.js",
3965 "id": "feature.planSelection.free.text",
3966 "start": {
3967 "column": 12,
3968 "line": 24
3969 }
3970 },
3971 {
3972 "defaultMessage": "!!!More services, no waiting - ideal for personal use.",
3973 "end": {
3974 "column": 3,
3975 "line": 31
3976 },
3977 "file": "src/features/planSelection/components/PlanSelection.js",
3978 "id": "feature.planSelection.personal.text",
3979 "start": {
3980 "column": 16,
3981 "line": 28
3982 }
3983 },
3984 {
3985 "defaultMessage": "!!!Unlimited services and professional features for you - and your team.",
3986 "end": {
3987 "column": 3,
3988 "line": 35
3989 },
3990 "file": "src/features/planSelection/components/PlanSelection.js",
3991 "id": "feature.planSelection.pro.text",
3992 "start": {
3993 "column": 20,
3994 "line": 32
3995 }
3996 },
3997 {
3998 "defaultMessage": "!!!Stay on Free",
3999 "end": {
4000 "column": 3,
4001 "line": 39
4002 },
4003 "file": "src/features/planSelection/components/PlanSelection.js",
4004 "id": "feature.planSelection.cta.stayOnFree",
4005 "start": {
4006 "column": 17,
4007 "line": 36
4008 }
4009 },
4010 {
4011 "defaultMessage": "!!!Downgrade to Free",
4012 "end": {
4013 "column": 3,
4014 "line": 43
4015 },
4016 "file": "src/features/planSelection/components/PlanSelection.js",
4017 "id": "feature.planSelection.cta.ctaDowngradeFree",
4018 "start": {
4019 "column": 20,
4020 "line": 40
4021 }
4022 },
4023 {
4024 "defaultMessage": "!!!Start my free 14-days Trial",
4025 "end": {
4026 "column": 3,
4027 "line": 47
4028 },
4029 "file": "src/features/planSelection/components/PlanSelection.js",
4030 "id": "feature.planSelection.cta.trial",
4031 "start": {
4032 "column": 15,
4033 "line": 44
4034 }
4035 },
4036 {
4037 "defaultMessage": "!!!Choose Personal",
4038 "end": {
4039 "column": 3,
4040 "line": 51
4041 },
4042 "file": "src/features/planSelection/components/PlanSelection.js",
4043 "id": "feature.planSelection.cta.upgradePersonal",
4044 "start": {
4045 "column": 23,
4046 "line": 48
4047 }
4048 },
4049 {
4050 "defaultMessage": "!!!Choose Professional",
4051 "end": {
4052 "column": 3,
4053 "line": 55
4054 },
4055 "file": "src/features/planSelection/components/PlanSelection.js",
4056 "id": "feature.planSelection.cta.upgradePro",
4057 "start": {
4058 "column": 18,
4059 "line": 52
4060 }
4061 },
4062 {
4063 "defaultMessage": "!!!Complete comparison of all plans",
4064 "end": {
4065 "column": 3,
4066 "line": 59
4067 },
4068 "file": "src/features/planSelection/components/PlanSelection.js",
4069 "id": "feature.planSelection.fullFeatureList",
4070 "start": {
4071 "column": 19,
4072 "line": 56
4073 }
4074 }
4075 ],
4076 "path": "src/features/planSelection/components/PlanSelection.json"
4077 },
4078 {
4079 "descriptors": [
4080 {
4081 "defaultMessage": "!!!per {interval}",
4082 "end": {
4083 "column": 3,
4084 "line": 19
4085 },
4086 "file": "src/features/planSelection/components/PlanTeaser.js",
4087 "id": "subscription.interval.per",
4088 "start": {
4089 "column": 7,
4090 "line": 16
4091 }
4092 },
4093 {
4094 "defaultMessage": "!!!Upgrade Account",
4095 "end": {
4096 "column": 3,
4097 "line": 23
4098 },
4099 "file": "src/features/planSelection/components/PlanTeaser.js",
4100 "id": "subscription.planItem.upgradeAccount",
4101 "start": {
4102 "column": 7,
4103 "line": 20
4104 }
4105 }
4106 ],
4107 "path": "src/features/planSelection/components/PlanTeaser.json"
4108 },
4109 {
4110 "descriptors": [
4111 {
4112 "defaultMessage": "!!!Downgrade your Franz Plan",
4113 "end": {
4114 "column": 3,
4115 "line": 19
4116 },
4117 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
4118 "id": "feature.planSelection.fullscreen.dialog.title",
4119 "start": {
4120 "column": 15,
4121 "line": 16
4122 }
4123 },
4124 {
4125 "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.",
4126 "end": {
4127 "column": 3,
4128 "line": 23
4129 },
4130 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
4131 "id": "feature.planSelection.fullscreen.dialog.message",
4132 "start": {
4133 "column": 17,
4134 "line": 20
4135 }
4136 },
4137 {
4138 "defaultMessage": "!!!Downgrade to Free",
4139 "end": {
4140 "column": 3,
4141 "line": 27
4142 },
4143 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
4144 "id": "feature.planSelection.fullscreen.dialog.cta.downgrade",
4145 "start": {
4146 "column": 22,
4147 "line": 24
4148 }
4149 },
4150 {
4151 "defaultMessage": "!!!Choose Personal",
4152 "end": {
4153 "column": 3,
4154 "line": 31
4155 },
4156 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
4157 "id": "feature.planSelection.fullscreen.dialog.cta.upgrade",
4158 "start": {
4159 "column": 20,
4160 "line": 28
4161 }
4162 }
4163 ],
4164 "path": "src/features/planSelection/containers/PlanSelectionScreen.json"
4165 },
4166 {
4167 "descriptors": [
4168 {
3837 "defaultMessage": "!!!Changes in Franz {version}", 4169 "defaultMessage": "!!!Changes in Franz {version}",
3838 "end": { 4170 "end": {
3839 "column": 3, 4171 "column": 3,
@@ -4487,7 +4819,7 @@
4487 { 4819 {
4488 "descriptors": [ 4820 "descriptors": [
4489 { 4821 {
4490 "defaultMessage": "!!!Franz Professional", 4822 "defaultMessage": "!!!Professional",
4491 "end": { 4823 "end": {
4492 "column": 3, 4824 "column": 3,
4493 "line": 8 4825 "line": 8
@@ -4500,7 +4832,7 @@
4500 } 4832 }
4501 }, 4833 },
4502 { 4834 {
4503 "defaultMessage": "!!!Franz Personal", 4835 "defaultMessage": "!!!Personal",
4504 "end": { 4836 "end": {
4505 "column": 3, 4837 "column": 3,
4506 "line": 12 4838 "line": 12
@@ -4513,7 +4845,7 @@
4513 } 4845 }
4514 }, 4846 },
4515 { 4847 {
4516 "defaultMessage": "!!!Franz Free", 4848 "defaultMessage": "!!!Free",
4517 "end": { 4849 "end": {
4518 "column": 3, 4850 "column": 3,
4519 "line": 16 4851 "line": 16
@@ -4526,7 +4858,7 @@
4526 } 4858 }
4527 }, 4859 },
4528 { 4860 {
4529 "defaultMessage": "!!!Franz Premium", 4861 "defaultMessage": "!!!Premium",
4530 "end": { 4862 "end": {
4531 "column": 3, 4863 "column": 3,
4532 "line": 20 4864 "line": 20
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index aea74768d..4e1b01419 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -9,6 +9,21 @@
9 "feature.delayApp.trial.headline": "Get the free Franz Professional 14 day trial and skip the line", 9 "feature.delayApp.trial.headline": "Get the free Franz Professional 14 day trial and skip the line",
10 "feature.delayApp.upgrade.action": "Get a Franz Supporter License", 10 "feature.delayApp.upgrade.action": "Get a Franz Supporter License",
11 "feature.delayApp.upgrade.actionShort": "Upgrade account", 11 "feature.delayApp.upgrade.actionShort": "Upgrade account",
12 "feature.planSelection.cta.ctaDowngradeFree": "Downgrade to Free",
13 "feature.planSelection.cta.stayOnFree": "Stay on Free",
14 "feature.planSelection.cta.trial": "Start my free 14-days Trial",
15 "feature.planSelection.cta.upgradePersonal": "Choose Personal",
16 "feature.planSelection.cta.upgradePro": "Choose Professional",
17 "feature.planSelection.free.text": "Basic functionality",
18 "feature.planSelection.fullFeatureList": "Complete comparison of all plans",
19 "feature.planSelection.fullscreen.dialog.cta.downgrade": "Downgrade to Free",
20 "feature.planSelection.fullscreen.dialog.cta.upgrade": "Choose Personal",
21 "feature.planSelection.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.",
22 "feature.planSelection.fullscreen.dialog.title": "Downgrade your Franz Plan",
23 "feature.planSelection.fullscreen.subheadline": "It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.",
24 "feature.planSelection.fullscreen.welcome": "Welcome back, {name}",
25 "feature.planSelection.personal.text": "More services, no waiting - ideal for personal use.",
26 "feature.planSelection.pro.text": "Unlimited services and professional features for you - and your team.",
12 "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.", 27 "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.",
13 "feature.shareFranz.action.email": "Send as email", 28 "feature.shareFranz.action.email": "Send as email",
14 "feature.shareFranz.action.facebook": "Share on Facebook", 29 "feature.shareFranz.action.facebook": "Share on Facebook",
@@ -123,30 +138,35 @@
123 "password.submit.label": "Submit", 138 "password.submit.label": "Submit",
124 "password.successInfo": "Please check your email", 139 "password.successInfo": "Please check your email",
125 "premiumFeature.button.upgradeAccount": "Upgrade account", 140 "premiumFeature.button.upgradeAccount": "Upgrade account",
141 "pricing.features.accountSync": "Account Synchronisation",
126 "pricing.features.adFree": "Forever ad-free", 142 "pricing.features.adFree": "Forever ad-free",
127 "pricing.features.appDelays": "No Waiting Screens", 143 "pricing.features.appDelays": "No Waiting Screens",
128 "pricing.features.customWebsites": "Add Custom Websites", 144 "pricing.features.customWebsites": "Add Custom Websites",
145 "pricing.features.desktopNotifications": "Desktop Notifications",
129 "pricing.features.onPremise": "On-premise & other Hosted Services", 146 "pricing.features.onPremise": "On-premise & other Hosted Services",
147 "pricing.features.recipes": "Choose from more than 70 Services",
130 "pricing.features.serviceProxies": "Service Proxies", 148 "pricing.features.serviceProxies": "Service Proxies",
131 "pricing.features.spellchecker": "Spellchecker support", 149 "pricing.features.spellchecker": "Spellchecker support",
132 "pricing.features.teamManagement": "Team Management", 150 "pricing.features.teamManagement": "Team Management",
133 "pricing.features.thirdPartyServices": "Install 3rd party services", 151 "pricing.features.thirdPartyServices": "Install 3rd party services",
134 "pricing.features.unlimitedServices": "Add unlimited services", 152 "pricing.features.unlimitedServices": "Add unlimited services",
153 "pricing.features.upToSixServices": "Add up to 6 services",
154 "pricing.features.upToThreeServices": "Add up to 3 services",
135 "pricing.features.workspaces": "Workspaces", 155 "pricing.features.workspaces": "Workspaces",
136 "pricing.plan.free": "Franz Free", 156 "pricing.plan.free": "Free",
137 "pricing.plan.legacy": "Franz Premium", 157 "pricing.plan.legacy": "Premium",
138 "pricing.plan.personal": "Franz Personal", 158 "pricing.plan.personal": "Personal",
139 "pricing.plan.personal-monthly": "Franz Personal Monthly", 159 "pricing.plan.personal-monthly": "Personal Monthly",
140 "pricing.plan.personal-yearly": "Franz Personal Yearly", 160 "pricing.plan.personal-yearly": "Personal Yearly",
141 "pricing.plan.pro": "Franz Professional", 161 "pricing.plan.pro": "Professional",
142 "pricing.plan.pro-monthly": "Franz Professional Monthly", 162 "pricing.plan.pro-monthly": "Professional Monthly",
143 "pricing.plan.pro-yearly": "Franz Professional Yearly", 163 "pricing.plan.pro-yearly": "Professional Yearly",
144 "pricing.trial.cta.accept": "Yes, upgrade my account to Franz Professional", 164 "pricing.trial.cta.accept": "Start my 14-day Franz Professional Trial ",
145 "pricing.trial.cta.skip": "Continue to Franz", 165 "pricing.trial.cta.skip": "Continue to Franz",
146 "pricing.trial.error": "Sorry, we could not activate your trial!", 166 "pricing.trial.error": "Sorry, we could not activate your trial!",
147 "pricing.trial.features.headline": "Franz Professional includes:", 167 "pricing.trial.features.headline": "Franz Professional includes:",
148 "pricing.trial.headline": "Franz Professional", 168 "pricing.trial.headline": "Franz Professional",
149 "pricing.trial.subheadline": "Your personal welcome offer:", 169 "pricing.trial.subheadline": "Here's a special welcome for you:",
150 "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days", 170 "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days",
151 "pricing.trial.terms.headline": "No strings attached", 171 "pricing.trial.terms.headline": "No strings attached",
152 "pricing.trial.terms.noCreditCard": "No credit card required", 172 "pricing.trial.terms.noCreditCard": "No credit card required",
@@ -353,6 +373,10 @@
353 "subscription.cta.allOptions": "See all options", 373 "subscription.cta.allOptions": "See all options",
354 "subscription.cta.choosePlan": "Choose your plan", 374 "subscription.cta.choosePlan": "Choose your plan",
355 "subscription.includedProFeatures": "The Franz Professional Plan includes:", 375 "subscription.includedProFeatures": "The Franz Professional Plan includes:",
376 "subscription.interval.per": "per {interval}",
377 "subscription.interval.perMonth": "per month",
378 "subscription.interval.perMonthPerUser": "per month & user",
379 "subscription.planItem.upgradeAccount": "Upgrade Account",
356 "subscription.teaser.includedFeatures": "Paid Franz Plans include:", 380 "subscription.teaser.includedFeatures": "Paid Franz Plans include:",
357 "subscription.teaser.intro": "Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!", 381 "subscription.teaser.intro": "Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
358 "subscriptionPopup.buttonCancel": "Cancel", 382 "subscriptionPopup.buttonCancel": "Cancel",
@@ -389,4 +413,4 @@
389 "workspaceDrawer.workspaceFeatureInfo": "<p>Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.</p><p>You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.</p>", 413 "workspaceDrawer.workspaceFeatureInfo": "<p>Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.</p><p>You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.</p>",
390 "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", 414 "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings",
391 "workspaces.switchingIndicator.switchingTo": "Switching to" 415 "workspaces.switchingIndicator.switchingTo": "Switching to"
392} \ No newline at end of file 416}
diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json
index f15617ca5..2d09c9ebb 100644
--- a/src/i18n/messages/src/components/auth/Pricing.json
+++ b/src/i18n/messages/src/components/auth/Pricing.json
@@ -14,7 +14,7 @@
14 }, 14 },
15 { 15 {
16 "id": "pricing.trial.subheadline", 16 "id": "pricing.trial.subheadline",
17 "defaultMessage": "!!!Your personal welcome offer:", 17 "defaultMessage": "!!!Here's a special welcome for you:",
18 "file": "src/components/auth/Pricing.js", 18 "file": "src/components/auth/Pricing.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 19,
@@ -79,7 +79,7 @@
79 }, 79 },
80 { 80 {
81 "id": "pricing.trial.cta.accept", 81 "id": "pricing.trial.cta.accept",
82 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", 82 "defaultMessage": "!!!Start my 14-day Franz Professional Trial",
83 "file": "src/components/auth/Pricing.js", 83 "file": "src/components/auth/Pricing.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 39,
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index 44cf4fab9..22f11cedd 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": 28, 7 "line": 29,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 31, 11 "line": 32,
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": 32, 20 "line": 33,
21 "column": 24 21 "column": 24
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 35, 24 "line": 36,
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": 36, 33 "line": 37,
34 "column": 26 34 "column": 26
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 39, 37 "line": 40,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json
index 497e299a4..3201115b3 100644
--- a/src/i18n/messages/src/components/ui/FeatureList.json
+++ b/src/i18n/messages/src/components/ui/FeatureList.json
@@ -1,14 +1,79 @@
1[ 1[
2 { 2 {
3 "id": "pricing.features.recipes",
4 "defaultMessage": "!!!Choose from more than 70 Services",
5 "file": "src/components/ui/FeatureList.js",
6 "start": {
7 "line": 9,
8 "column": 20
9 },
10 "end": {
11 "line": 12,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.features.accountSync",
17 "defaultMessage": "!!!Account Synchronisation",
18 "file": "src/components/ui/FeatureList.js",
19 "start": {
20 "line": 13,
21 "column": 15
22 },
23 "end": {
24 "line": 16,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.features.desktopNotifications",
30 "defaultMessage": "!!!Desktop Notifications",
31 "file": "src/components/ui/FeatureList.js",
32 "start": {
33 "line": 17,
34 "column": 24
35 },
36 "end": {
37 "line": 20,
38 "column": 3
39 }
40 },
41 {
3 "id": "pricing.features.unlimitedServices", 42 "id": "pricing.features.unlimitedServices",
4 "defaultMessage": "!!!Add unlimited services", 43 "defaultMessage": "!!!Add unlimited services",
5 "file": "src/components/ui/FeatureList.js", 44 "file": "src/components/ui/FeatureList.js",
6 "start": { 45 "start": {
7 "line": 8, 46 "line": 21,
8 "column": 21 47 "column": 21
9 }, 48 },
10 "end": { 49 "end": {
11 "line": 11, 50 "line": 24,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.features.upToThreeServices",
56 "defaultMessage": "!!!Add up to 3 services",
57 "file": "src/components/ui/FeatureList.js",
58 "start": {
59 "line": 25,
60 "column": 21
61 },
62 "end": {
63 "line": 28,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.features.upToSixServices",
69 "defaultMessage": "!!!Add up to 6 services",
70 "file": "src/components/ui/FeatureList.js",
71 "start": {
72 "line": 29,
73 "column": 19
74 },
75 "end": {
76 "line": 32,
12 "column": 3 77 "column": 3
13 } 78 }
14 }, 79 },
@@ -17,11 +82,11 @@
17 "defaultMessage": "!!!Spellchecker support", 82 "defaultMessage": "!!!Spellchecker support",
18 "file": "src/components/ui/FeatureList.js", 83 "file": "src/components/ui/FeatureList.js",
19 "start": { 84 "start": {
20 "line": 12, 85 "line": 33,
21 "column": 16 86 "column": 16
22 }, 87 },
23 "end": { 88 "end": {
24 "line": 15, 89 "line": 36,
25 "column": 3 90 "column": 3
26 } 91 }
27 }, 92 },
@@ -30,11 +95,11 @@
30 "defaultMessage": "!!!Workspaces", 95 "defaultMessage": "!!!Workspaces",
31 "file": "src/components/ui/FeatureList.js", 96 "file": "src/components/ui/FeatureList.js",
32 "start": { 97 "start": {
33 "line": 16, 98 "line": 37,
34 "column": 14 99 "column": 14
35 }, 100 },
36 "end": { 101 "end": {
37 "line": 19, 102 "line": 40,
38 "column": 3 103 "column": 3
39 } 104 }
40 }, 105 },
@@ -43,11 +108,11 @@
43 "defaultMessage": "!!!Add Custom Websites", 108 "defaultMessage": "!!!Add Custom Websites",
44 "file": "src/components/ui/FeatureList.js", 109 "file": "src/components/ui/FeatureList.js",
45 "start": { 110 "start": {
46 "line": 20, 111 "line": 41,
47 "column": 18 112 "column": 18
48 }, 113 },
49 "end": { 114 "end": {
50 "line": 23, 115 "line": 44,
51 "column": 3 116 "column": 3
52 } 117 }
53 }, 118 },
@@ -56,11 +121,11 @@
56 "defaultMessage": "!!!On-premise & other Hosted Services", 121 "defaultMessage": "!!!On-premise & other Hosted Services",
57 "file": "src/components/ui/FeatureList.js", 122 "file": "src/components/ui/FeatureList.js",
58 "start": { 123 "start": {
59 "line": 24, 124 "line": 45,
60 "column": 13 125 "column": 13
61 }, 126 },
62 "end": { 127 "end": {
63 "line": 27, 128 "line": 48,
64 "column": 3 129 "column": 3
65 } 130 }
66 }, 131 },
@@ -69,11 +134,11 @@
69 "defaultMessage": "!!!Install 3rd party services", 134 "defaultMessage": "!!!Install 3rd party services",
70 "file": "src/components/ui/FeatureList.js", 135 "file": "src/components/ui/FeatureList.js",
71 "start": { 136 "start": {
72 "line": 28, 137 "line": 49,
73 "column": 22 138 "column": 22
74 }, 139 },
75 "end": { 140 "end": {
76 "line": 31, 141 "line": 52,
77 "column": 3 142 "column": 3
78 } 143 }
79 }, 144 },
@@ -82,11 +147,11 @@
82 "defaultMessage": "!!!Service Proxies", 147 "defaultMessage": "!!!Service Proxies",
83 "file": "src/components/ui/FeatureList.js", 148 "file": "src/components/ui/FeatureList.js",
84 "start": { 149 "start": {
85 "line": 32, 150 "line": 53,
86 "column": 18 151 "column": 18
87 }, 152 },
88 "end": { 153 "end": {
89 "line": 35, 154 "line": 56,
90 "column": 3 155 "column": 3
91 } 156 }
92 }, 157 },
@@ -95,11 +160,11 @@
95 "defaultMessage": "!!!Team Management", 160 "defaultMessage": "!!!Team Management",
96 "file": "src/components/ui/FeatureList.js", 161 "file": "src/components/ui/FeatureList.js",
97 "start": { 162 "start": {
98 "line": 36, 163 "line": 57,
99 "column": 18 164 "column": 18
100 }, 165 },
101 "end": { 166 "end": {
102 "line": 39, 167 "line": 60,
103 "column": 3 168 "column": 3
104 } 169 }
105 }, 170 },
@@ -108,11 +173,11 @@
108 "defaultMessage": "!!!No Waiting Screens", 173 "defaultMessage": "!!!No Waiting Screens",
109 "file": "src/components/ui/FeatureList.js", 174 "file": "src/components/ui/FeatureList.js",
110 "start": { 175 "start": {
111 "line": 40, 176 "line": 61,
112 "column": 13 177 "column": 13
113 }, 178 },
114 "end": { 179 "end": {
115 "line": 43, 180 "line": 64,
116 "column": 3 181 "column": 3
117 } 182 }
118 }, 183 },
@@ -121,11 +186,11 @@
121 "defaultMessage": "!!!Forever ad-free", 186 "defaultMessage": "!!!Forever ad-free",
122 "file": "src/components/ui/FeatureList.js", 187 "file": "src/components/ui/FeatureList.js",
123 "start": { 188 "start": {
124 "line": 44, 189 "line": 65,
125 "column": 10 190 "column": 10
126 }, 191 },
127 "end": { 192 "end": {
128 "line": 47, 193 "line": 68,
129 "column": 3 194 "column": 3
130 } 195 }
131 } 196 }
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanItem.json b/src/i18n/messages/src/features/planSelection/components/PlanItem.json
new file mode 100644
index 000000000..839686390
--- /dev/null
+++ b/src/i18n/messages/src/features/planSelection/components/PlanItem.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "subscription.interval.perMonth",
4 "defaultMessage": "!!!per month",
5 "file": "src/features/planSelection/components/PlanItem.js",
6 "start": {
7 "line": 19,
8 "column": 12
9 },
10 "end": {
11 "line": 22,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.interval.perMonthPerUser",
17 "defaultMessage": "!!!per month & user",
18 "file": "src/features/planSelection/components/PlanItem.js",
19 "start": {
20 "line": 23,
21 "column": 19
22 },
23 "end": {
24 "line": 26,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanSelection.json b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json
new file mode 100644
index 000000000..3dc4f74f4
--- /dev/null
+++ b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json
@@ -0,0 +1,145 @@
1[
2 {
3 "id": "feature.planSelection.fullscreen.welcome",
4 "defaultMessage": "!!!Welcome back, {name}",
5 "file": "src/features/planSelection/components/PlanSelection.js",
6 "start": {
7 "line": 16,
8 "column": 11
9 },
10 "end": {
11 "line": 19,
12 "column": 3
13 }
14 },
15 {
16 "id": "feature.planSelection.fullscreen.subheadline",
17 "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.",
18 "file": "src/features/planSelection/components/PlanSelection.js",
19 "start": {
20 "line": 20,
21 "column": 15
22 },
23 "end": {
24 "line": 23,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.planSelection.free.text",
30 "defaultMessage": "!!!Basic functionality",
31 "file": "src/features/planSelection/components/PlanSelection.js",
32 "start": {
33 "line": 24,
34 "column": 12
35 },
36 "end": {
37 "line": 27,
38 "column": 3
39 }
40 },
41 {
42 "id": "feature.planSelection.personal.text",
43 "defaultMessage": "!!!More services, no waiting - ideal for personal use.",
44 "file": "src/features/planSelection/components/PlanSelection.js",
45 "start": {
46 "line": 28,
47 "column": 16
48 },
49 "end": {
50 "line": 31,
51 "column": 3
52 }
53 },
54 {
55 "id": "feature.planSelection.pro.text",
56 "defaultMessage": "!!!Unlimited services and professional features for you - and your team.",
57 "file": "src/features/planSelection/components/PlanSelection.js",
58 "start": {
59 "line": 32,
60 "column": 20
61 },
62 "end": {
63 "line": 35,
64 "column": 3
65 }
66 },
67 {
68 "id": "feature.planSelection.cta.stayOnFree",
69 "defaultMessage": "!!!Stay on Free",
70 "file": "src/features/planSelection/components/PlanSelection.js",
71 "start": {
72 "line": 36,
73 "column": 17
74 },
75 "end": {
76 "line": 39,
77 "column": 3
78 }
79 },
80 {
81 "id": "feature.planSelection.cta.ctaDowngradeFree",
82 "defaultMessage": "!!!Downgrade to Free",
83 "file": "src/features/planSelection/components/PlanSelection.js",
84 "start": {
85 "line": 40,
86 "column": 20
87 },
88 "end": {
89 "line": 43,
90 "column": 3
91 }
92 },
93 {
94 "id": "feature.planSelection.cta.trial",
95 "defaultMessage": "!!!Start my free 14-days Trial",
96 "file": "src/features/planSelection/components/PlanSelection.js",
97 "start": {
98 "line": 44,
99 "column": 15
100 },
101 "end": {
102 "line": 47,
103 "column": 3
104 }
105 },
106 {
107 "id": "feature.planSelection.cta.upgradePersonal",
108 "defaultMessage": "!!!Choose Personal",
109 "file": "src/features/planSelection/components/PlanSelection.js",
110 "start": {
111 "line": 48,
112 "column": 23
113 },
114 "end": {
115 "line": 51,
116 "column": 3
117 }
118 },
119 {
120 "id": "feature.planSelection.cta.upgradePro",
121 "defaultMessage": "!!!Choose Professional",
122 "file": "src/features/planSelection/components/PlanSelection.js",
123 "start": {
124 "line": 52,
125 "column": 18
126 },
127 "end": {
128 "line": 55,
129 "column": 3
130 }
131 },
132 {
133 "id": "feature.planSelection.fullFeatureList",
134 "defaultMessage": "!!!Complete comparison of all plans",
135 "file": "src/features/planSelection/components/PlanSelection.js",
136 "start": {
137 "line": 56,
138 "column": 19
139 },
140 "end": {
141 "line": 59,
142 "column": 3
143 }
144 }
145] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json
new file mode 100644
index 000000000..015304a2e
--- /dev/null
+++ b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "subscription.interval.per",
4 "defaultMessage": "!!!per {interval}",
5 "file": "src/features/planSelection/components/PlanTeaser.js",
6 "start": {
7 "line": 16,
8 "column": 7
9 },
10 "end": {
11 "line": 19,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.planItem.upgradeAccount",
17 "defaultMessage": "!!!Upgrade Account",
18 "file": "src/features/planSelection/components/PlanTeaser.js",
19 "start": {
20 "line": 20,
21 "column": 7
22 },
23 "end": {
24 "line": 23,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json
new file mode 100644
index 000000000..04b2144b4
--- /dev/null
+++ b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json
@@ -0,0 +1,54 @@
1[
2 {
3 "id": "feature.planSelection.fullscreen.dialog.title",
4 "defaultMessage": "!!!Downgrade your Franz Plan",
5 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
6 "start": {
7 "line": 16,
8 "column": 15
9 },
10 "end": {
11 "line": 19,
12 "column": 3
13 }
14 },
15 {
16 "id": "feature.planSelection.fullscreen.dialog.message",
17 "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.",
18 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
19 "start": {
20 "line": 20,
21 "column": 17
22 },
23 "end": {
24 "line": 23,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.planSelection.fullscreen.dialog.cta.downgrade",
30 "defaultMessage": "!!!Downgrade to Free",
31 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
32 "start": {
33 "line": 24,
34 "column": 22
35 },
36 "end": {
37 "line": 27,
38 "column": 3
39 }
40 },
41 {
42 "id": "feature.planSelection.fullscreen.dialog.cta.upgrade",
43 "defaultMessage": "!!!Choose Personal",
44 "file": "src/features/planSelection/containers/PlanSelectionScreen.js",
45 "start": {
46 "line": 28,
47 "column": 20
48 },
49 "end": {
50 "line": 31,
51 "column": 3
52 }
53 }
54] \ 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
index df8ee19e3..3f3e7e85d 100644
--- a/src/i18n/messages/src/helpers/plan-helpers.json
+++ b/src/i18n/messages/src/helpers/plan-helpers.json
@@ -1,7 +1,7 @@
1[ 1[
2 { 2 {
3 "id": "pricing.plan.pro", 3 "id": "pricing.plan.pro",
4 "defaultMessage": "!!!Franz Professional", 4 "defaultMessage": "!!!Professional",
5 "file": "src/helpers/plan-helpers.js", 5 "file": "src/helpers/plan-helpers.js",
6 "start": { 6 "start": {
7 "line": 5, 7 "line": 5,
@@ -14,7 +14,7 @@
14 }, 14 },
15 { 15 {
16 "id": "pricing.plan.personal", 16 "id": "pricing.plan.personal",
17 "defaultMessage": "!!!Franz Personal", 17 "defaultMessage": "!!!Personal",
18 "file": "src/helpers/plan-helpers.js", 18 "file": "src/helpers/plan-helpers.js",
19 "start": { 19 "start": {
20 "line": 9, 20 "line": 9,
@@ -27,7 +27,7 @@
27 }, 27 },
28 { 28 {
29 "id": "pricing.plan.free", 29 "id": "pricing.plan.free",
30 "defaultMessage": "!!!Franz Free", 30 "defaultMessage": "!!!Free",
31 "file": "src/helpers/plan-helpers.js", 31 "file": "src/helpers/plan-helpers.js",
32 "start": { 32 "start": {
33 "line": 13, 33 "line": 13,
@@ -40,7 +40,7 @@
40 }, 40 },
41 { 41 {
42 "id": "pricing.plan.legacy", 42 "id": "pricing.plan.legacy",
43 "defaultMessage": "!!!Franz Premium", 43 "defaultMessage": "!!!Premium",
44 "file": "src/helpers/plan-helpers.js", 44 "file": "src/helpers/plan-helpers.js",
45 "start": { 45 "start": {
46 "line": 17, 46 "line": 17,
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index cf28b6bec..bffcb01bc 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -19,6 +19,7 @@ import settingsWS from '../features/settingsWS';
19import serviceLimit from '../features/serviceLimit'; 19import serviceLimit from '../features/serviceLimit';
20import communityRecipes from '../features/communityRecipes'; 20import communityRecipes from '../features/communityRecipes';
21import todos from '../features/todos'; 21import todos from '../features/todos';
22import planSelection from '../features/planSelection';
22 23
23import { DEFAULT_FEATURES_CONFIG } from '../config'; 24import { DEFAULT_FEATURES_CONFIG } from '../config';
24 25
@@ -81,5 +82,6 @@ export default class FeaturesStore extends Store {
81 serviceLimit(this.stores, this.actions); 82 serviceLimit(this.stores, this.actions);
82 communityRecipes(this.stores, this.actions); 83 communityRecipes(this.stores, this.actions);
83 todos(this.stores, this.actions); 84 todos(this.stores, this.actions);
85 planSelection(this.stores, this.actions);
84 } 86 }
85} 87}
diff --git a/src/stores/index.js b/src/stores/index.js
index 10dd56665..4eeef7982 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -15,6 +15,7 @@ import { announcementsStore } from '../features/announcements';
15import { serviceLimitStore } from '../features/serviceLimit'; 15import { serviceLimitStore } from '../features/serviceLimit';
16import { communityRecipesStore } from '../features/communityRecipes'; 16import { communityRecipesStore } from '../features/communityRecipes';
17import { todosStore } from '../features/todos'; 17import { todosStore } from '../features/todos';
18import { planSelectionStore } from '../features/planSelection';
18 19
19export default (api, actions, router) => { 20export default (api, actions, router) => {
20 const stores = {}; 21 const stores = {};
@@ -37,6 +38,7 @@ export default (api, actions, router) => {
37 serviceLimit: serviceLimitStore, 38 serviceLimit: serviceLimitStore,
38 communityRecipes: communityRecipesStore, 39 communityRecipes: communityRecipesStore,
39 todos: todosStore, 40 todos: todosStore,
41 planSelection: planSelectionStore,
40 }); 42 });
41 // Initialize all stores 43 // Initialize all stores
42 Object.keys(stores).forEach((name) => { 44 Object.keys(stores).forEach((name) => {