aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.eslintrc2
-rw-r--r--package-lock.json27
-rw-r--r--package.json2
-rw-r--r--packages/forms/package.json4
-rw-r--r--packages/forms/src/button/index.tsx12
-rw-r--r--packages/theme/src/themes/dark/index.ts12
-rw-r--r--packages/theme/src/themes/default/index.ts14
-rw-r--r--packages/ui/package.json4
-rw-r--r--packages/ui/src/badge/ProBadge.tsx8
-rw-r--r--packages/ui/src/icon/index.tsx12
-rw-r--r--packages/ui/src/infobox/index.tsx5
-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/assets/images/workspaces/teaser_dark.pngbin0 -> 179047 bytes
-rw-r--r--src/assets/images/workspaces/teaser_light.pngbin0 -> 182321 bytes
-rw-r--r--src/components/TrialActivationInfoBar.js94
-rw-r--r--src/components/auth/Pricing.js246
-rw-r--r--src/components/auth/Signup.js31
-rw-r--r--src/components/layout/AppLayout.js27
-rw-r--r--src/components/services/content/ServiceRestricted.js78
-rw-r--r--src/components/services/content/ServiceView.js25
-rw-r--r--src/components/services/content/Services.js48
-rw-r--r--src/components/settings/account/AccountDashboard.js206
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js8
-rw-r--r--src/components/settings/recipes/RecipeItem.js2
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js194
-rw-r--r--src/components/settings/services/EditServiceForm.js17
-rw-r--r--src/components/settings/services/ServicesDashboard.js2
-rw-r--r--src/components/settings/settings/EditSettingsForm.js6
-rw-r--r--src/components/settings/team/TeamDashboard.js10
-rw-r--r--src/components/subscription/SubscriptionForm.js226
-rw-r--r--src/components/subscription/SubscriptionPopup.js4
-rw-r--r--src/components/subscription/TrialForm.js115
-rw-r--r--src/components/ui/ActivateTrialButton/index.js115
-rw-r--r--src/components/ui/FeatureItem.js37
-rw-r--r--src/components/ui/FeatureList.js89
-rw-r--r--src/components/ui/Modal/index.js3
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js22
-rw-r--r--src/config.js32
-rw-r--r--src/containers/auth/PricingScreen.js40
-rw-r--r--src/containers/layout/AppLayoutContainer.js6
-rw-r--r--src/containers/settings/AccountScreen.js4
-rw-r--r--src/containers/settings/EditServiceScreen.js4
-rw-r--r--src/containers/settings/EditSettingsScreen.js6
-rw-r--r--src/containers/settings/RecipesScreen.js40
-rw-r--r--src/containers/settings/TeamScreen.js1
-rw-r--r--src/containers/subscription/SubscriptionFormScreen.js99
-rw-r--r--src/containers/subscription/SubscriptionPopupScreen.js3
-rw-r--r--src/features/basicAuth/Component.js1
-rw-r--r--src/features/communityRecipes/index.js28
-rw-r--r--src/features/communityRecipes/store.js31
-rw-r--r--src/features/delayApp/Component.js43
-rw-r--r--src/features/delayApp/index.js2
-rw-r--r--src/features/serviceLimit/components/LimitReachedInfobox.js78
-rw-r--r--src/features/serviceLimit/index.js33
-rw-r--r--src/features/serviceLimit/store.js41
-rw-r--r--src/features/serviceProxy/index.js8
-rw-r--r--src/features/shareFranz/Component.js13
-rw-r--r--src/features/spellchecker/index.js8
-rw-r--r--src/features/todos/components/TodosWebview.js105
-rw-r--r--src/features/todos/containers/TodosScreen.js13
-rw-r--r--src/features/todos/store.js2
-rw-r--r--src/features/workspaces/components/WorkspaceDrawer.js7
-rw-r--r--src/features/workspaces/components/WorkspaceSwitchingIndicator.js8
-rw-r--r--src/features/workspaces/components/WorkspacesDashboard.js50
-rw-r--r--src/features/workspaces/store.js9
-rw-r--r--src/helpers/plan-helpers.js45
-rw-r--r--src/i18n/locales/defaultMessages.json1359
-rw-r--r--src/i18n/locales/en-US.json86
-rw-r--r--src/i18n/messages/src/components/TrialActivationInfoBar.json15
-rw-r--r--src/i18n/messages/src/components/auth/Pricing.json101
-rw-r--r--src/i18n/messages/src/components/auth/Signup.json57
-rw-r--r--src/i18n/messages/src/components/layout/AppLayout.json12
-rw-r--r--src/i18n/messages/src/components/services/content/ServiceRestricted.json67
-rw-r--r--src/i18n/messages/src/components/services/content/Services.json8
-rw-r--r--src/i18n/messages/src/components/settings/account/AccountDashboard.json124
-rw-r--r--src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json32
-rw-r--r--src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json116
-rw-r--r--src/i18n/messages/src/components/settings/services/EditServiceForm.json88
-rw-r--r--src/i18n/messages/src/components/settings/services/ServicesDashboard.json36
-rw-r--r--src/i18n/messages/src/components/subscription/SubscriptionForm.json164
-rw-r--r--src/i18n/messages/src/components/subscription/TrialForm.json93
-rw-r--r--src/i18n/messages/src/components/ui/ActivateTrialButton/index.json54
-rw-r--r--src/i18n/messages/src/components/ui/FeatureList.json132
-rw-r--r--src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json4
-rw-r--r--src/i18n/messages/src/features/delayApp/Component.json40
-rw-r--r--src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json15
-rw-r--r--src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json28
-rw-r--r--src/i18n/messages/src/features/shareFranz/Component.json28
-rw-r--r--src/i18n/messages/src/features/todos/components/TodosWebview.json41
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json32
-rw-r--r--src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json32
-rw-r--r--src/i18n/messages/src/helpers/plan-helpers.json54
-rw-r--r--src/i18n/messages/src/helpers/pricing-helpers.json80
-rw-r--r--src/index.js29
-rw-r--r--src/models/Service.js15
-rw-r--r--src/models/User.js14
-rw-r--r--src/stores/AppStore.js2
-rw-r--r--src/stores/FeaturesStore.js4
-rw-r--r--src/stores/ServicesStore.js39
-rw-r--r--src/stores/UIStore.js1
-rw-r--r--src/stores/UserStore.js67
-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
-rw-r--r--uidev/src/stories/button.stories.tsx6
-rw-r--r--uidev/src/stories/icon.stories.tsx47
-rw-r--r--uidev/src/stories/infobox.stories.tsx17
111 files changed, 4142 insertions, 1498 deletions
diff --git a/.eslintrc b/.eslintrc
index 47ecfb054..6253c0837 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -17,7 +17,7 @@
17 "extensions": [".js", ".jsx"] 17 "extensions": [".js", ".jsx"]
18 }], 18 }],
19 "react/forbid-prop-types": 0, 19 "react/forbid-prop-types": 0,
20 "react/destructuring-assignment": 1, 20 "react/destructuring-assignment": 0,
21 "prefer-destructuring": 1, 21 "prefer-destructuring": 1,
22 "no-underscore-dangle": 0, 22 "no-underscore-dangle": 0,
23 "max-len": 0, 23 "max-len": 0,
diff --git a/package-lock.json b/package-lock.json
index 607ebc31a..048425eb6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2149,9 +2149,9 @@
2149 } 2149 }
2150 }, 2150 },
2151 "@mdi/js": { 2151 "@mdi/js": {
2152 "version": "3.4.93", 2152 "version": "4.2.95",
2153 "resolved": "https://registry.npmjs.org/@mdi/js/-/js-3.4.93.tgz", 2153 "resolved": "https://registry.npmjs.org/@mdi/js/-/js-4.2.95.tgz",
2154 "integrity": "sha512-SEzolEqT8ErlWdHz4AAtQ1lTfAnM6j67Ppm6k5s/I1aIuuoFP/D8d/z838C28xHO1KOqrsS1fw2wlf6fRiEEJA==" 2154 "integrity": "sha512-3qqOZx2HkrQEUc9fr5MiQWlokwmO8TK5bQZ2EP1Rg0q2Q507jy+fUeL8lb9ko2ossYqoPnugIr7jI0/O7uhlrA=="
2155 }, 2155 },
2156 "@mdi/react": { 2156 "@mdi/react": {
2157 "version": "1.1.0", 2157 "version": "1.1.0",
@@ -2178,6 +2178,10 @@
2178 "react-loader": "^2.4.5" 2178 "react-loader": "^2.4.5"
2179 }, 2179 },
2180 "dependencies": { 2180 "dependencies": {
2181 "@mdi/js": {
2182 "version": "3.9.97",
2183 "bundled": true
2184 },
2181 "@meetfranz/theme": { 2185 "@meetfranz/theme": {
2182 "version": "1.0.14", 2186 "version": "1.0.14",
2183 "bundled": true, 2187 "bundled": true,
@@ -2202,6 +2206,10 @@
2202 "react-loader": "^2.4.5" 2206 "react-loader": "^2.4.5"
2203 }, 2207 },
2204 "dependencies": { 2208 "dependencies": {
2209 "@mdi/js": {
2210 "version": "3.9.97",
2211 "bundled": true
2212 },
2205 "@meetfranz/theme": { 2213 "@meetfranz/theme": {
2206 "version": "1.0.14", 2214 "version": "1.0.14",
2207 "bundled": true, 2215 "bundled": true,
@@ -16748,6 +16756,14 @@
16748 "react-transition-group": "^1.2.0" 16756 "react-transition-group": "^1.2.0"
16749 } 16757 }
16750 }, 16758 },
16759 "react-confetti": {
16760 "version": "3.1.0",
16761 "resolved": "https://registry.npmjs.org/react-confetti/-/react-confetti-3.1.0.tgz",
16762 "integrity": "sha512-T1DKt09C9rrf2BJ0OxFu8saPohFalOJfOgci11/ePz6DxbY+0cd/3CMimxj/ZITl7jQWnmC/bNWBgGheke3WZQ==",
16763 "requires": {
16764 "tween-functions": "^1.2.0"
16765 }
16766 },
16751 "react-dom": { 16767 "react-dom": {
16752 "version": "16.6.3", 16768 "version": "16.6.3",
16753 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz", 16769 "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.6.3.tgz",
@@ -19899,6 +19915,11 @@
19899 "safe-buffer": "^5.0.1" 19915 "safe-buffer": "^5.0.1"
19900 } 19916 }
19901 }, 19917 },
19918 "tween-functions": {
19919 "version": "1.2.0",
19920 "resolved": "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz",
19921 "integrity": "sha1-GuOlDnxguz3vd06scHrLynO7w/8="
19922 },
19902 "tweetnacl": { 19923 "tweetnacl": {
19903 "version": "0.14.5", 19924 "version": "0.14.5",
19904 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 19925 "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
diff --git a/package.json b/package.json
index 639d078a0..d30fa08c5 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
36 "dependencies": { 36 "dependencies": {
37 "@babel/polyfill": "7.4.4", 37 "@babel/polyfill": "7.4.4",
38 "@babel/runtime": "7.4.5", 38 "@babel/runtime": "7.4.5",
39 "@mdi/js": "4.2.95",
39 "@meetfranz/electron-notification-state": "1.0.0", 40 "@meetfranz/electron-notification-state": "1.0.0",
40 "@meetfranz/forms": "file:packages/forms", 41 "@meetfranz/forms": "file:packages/forms",
41 "@meetfranz/theme": "file:packages/theme", 42 "@meetfranz/theme": "file:packages/theme",
@@ -71,6 +72,7 @@
71 "prop-types": "^15.5.10", 72 "prop-types": "^15.5.10",
72 "react": "16.6.3", 73 "react": "16.6.3",
73 "react-addons-css-transition-group": "15.6.2", 74 "react-addons-css-transition-group": "15.6.2",
75 "react-confetti": "3.1.0",
74 "react-dom": "16.6.3", 76 "react-dom": "16.6.3",
75 "react-dropzone": "7.0.1", 77 "react-dropzone": "7.0.1",
76 "react-electron-web-view": "^2.0.1", 78 "react-electron-web-view": "^2.0.1",
diff --git a/packages/forms/package.json b/packages/forms/package.json
index d50f4c756..bb76462d8 100644
--- a/packages/forms/package.json
+++ b/packages/forms/package.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "@meetfranz/forms", 2 "name": "@meetfranz/forms",
3 "version": "1.0.16", 3 "version": "1.1.0",
4 "description": "React form components for Franz", 4 "description": "React form components for Franz",
5 "main": "lib/index.js", 5 "main": "lib/index.js",
6 "scripts": { 6 "scripts": {
@@ -35,5 +35,5 @@
35 "react-dom": "16.7.0", 35 "react-dom": "16.7.0",
36 "react-jss": "^8.6.1" 36 "react-jss": "^8.6.1"
37 }, 37 },
38 "gitHead": "9f2ab40b7602bc3df26ebb093b484b9917768f69" 38 "gitHead": "e1e46986d902adc4c19ee009016290f9733a7d61"
39} 39}
diff --git a/packages/forms/src/button/index.tsx b/packages/forms/src/button/index.tsx
index 9faedc8f1..b53c2da05 100644
--- a/packages/forms/src/button/index.tsx
+++ b/packages/forms/src/button/index.tsx
@@ -1,4 +1,3 @@
1import * as mdiIcons from '@mdi/js';
2import Icon from '@mdi/react'; 1import Icon from '@mdi/react';
3import { Theme } from '@meetfranz/theme'; 2import { Theme } from '@meetfranz/theme';
4import classnames from 'classnames'; 3import classnames from 'classnames';
@@ -21,7 +20,7 @@ interface IProps extends IFormField, IWithStyle {
21 stretch?: boolean; 20 stretch?: boolean;
22 loaded?: boolean; 21 loaded?: boolean;
23 busy?: boolean; 22 busy?: boolean;
24 icon?: keyof typeof mdiIcons; 23 icon?: string;
25 href?: string; 24 href?: string;
26 target?: string; 25 target?: string;
27} 26}
@@ -175,7 +174,7 @@ class ButtonComponent extends Component<IProps> {
175 onClick, 174 onClick,
176 buttonType, 175 buttonType,
177 loaded, 176 loaded,
178 icon: iconName, 177 icon,
179 busy: busyProp, 178 busy: busyProp,
180 href, 179 href,
181 target, 180 target,
@@ -185,13 +184,6 @@ class ButtonComponent extends Component<IProps> {
185 busy, 184 busy,
186 } = this.state; 185 } = this.state;
187 186
188 let icon = '';
189 if (iconName && mdiIcons[iconName]) {
190 icon = mdiIcons[iconName];
191 } else if (iconName && !mdiIcons[iconName]) {
192 console.warn(`Icon '${iconName}' was not found`);
193 }
194
195 let showLoader = false; 187 let showLoader = false;
196 if (loaded) { 188 if (loaded) {
197 showLoader = !loaded; 189 showLoader = !loaded;
diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts
index d29345298..e94f54c55 100644
--- a/packages/theme/src/themes/dark/index.ts
+++ b/packages/theme/src/themes/dark/index.ts
@@ -11,6 +11,8 @@ export const colorBackgroundSubscriptionContainer = legacyStyles.themeBrandInfo;
11export const colorHeadline = legacyStyles.darkThemeTextColor; 11export const colorHeadline = legacyStyles.darkThemeTextColor;
12export const colorText = legacyStyles.darkThemeTextColor; 12export const colorText = legacyStyles.darkThemeTextColor;
13 13
14export const defaultContentBorder = legacyStyles.themeGrayDark;
15
14// Loader 16// Loader
15export const colorFullscreenLoaderSpinner = '#FFF'; 17export const colorFullscreenLoaderSpinner = '#FFF';
16export const colorWebviewLoaderBackground = color(legacyStyles.darkThemeGrayDarkest).alpha(0.5).rgb().string(); 18export const colorWebviewLoaderBackground = color(legacyStyles.darkThemeGrayDarkest).alpha(0.5).rgb().string();
@@ -119,6 +121,16 @@ export const announcements = merge({}, defaultStyles.announcements, {
119 }, 121 },
120}); 122});
121 123
124// Signup
125export const signup = merge({}, defaultStyles.signup, {
126 pricing: {
127 feature: {
128 background: legacyStyles.darkThemeGrayLight,
129 border: color(legacyStyles.darkThemeGrayLight).lighten(0.2).hex(),
130 },
131 },
132});
133
122// Todos 134// Todos
123export const todos = merge({}, defaultStyles.todos, { 135export const todos = merge({}, defaultStyles.todos, {
124 todosLayer: { 136 todosLayer: {
diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts
index 9f9c39f9a..4e042afce 100644
--- a/packages/theme/src/themes/default/index.ts
+++ b/packages/theme/src/themes/default/index.ts
@@ -28,6 +28,8 @@ export const colorHeadline = legacyStyles.themeGrayDark;
28 28
29export const colorText = legacyStyles.themeTextColor; 29export const colorText = legacyStyles.themeTextColor;
30 30
31export const defaultContentBorder = color(legacyStyles.themeGrayLighter).darken(0.1).rgb().string();
32
31// Subscription Container Component 33// Subscription Container Component
32export const colorSubscriptionContainerBackground = 'none'; 34export const colorSubscriptionContainerBackground = 'none';
33export const colorSubscriptionContainerBorder = `1px solid ${brandPrimary}`; 35export const colorSubscriptionContainerBorder = `1px solid ${brandPrimary}`;
@@ -208,6 +210,16 @@ export const announcements = {
208 }, 210 },
209}; 211};
210 212
213// Signup
214export const signup = {
215 pricing: {
216 feature: {
217 background: legacyStyles.themeGrayLightest,
218 border: legacyStyles.themeGrayLighter,
219 },
220 },
221};
222
211// Todos 223// Todos
212export const todos = { 224export const todos = {
213 todosLayer: { 225 todosLayer: {
@@ -223,5 +235,5 @@ export const todos = {
223 }, 235 },
224 resizeHandler: { 236 resizeHandler: {
225 backgroundHover: styleTypes.primary.accent, 237 backgroundHover: styleTypes.primary.accent,
226 } 238 },
227}; 239};
diff --git a/packages/ui/package.json b/packages/ui/package.json
index a851ed9cf..4f42b21b5 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "@meetfranz/ui", 2 "name": "@meetfranz/ui",
3 "version": "0.0.9", 3 "version": "1.0.0",
4 "description": "React UI components for Franz", 4 "description": "React UI components for Franz",
5 "main": "lib/index.js", 5 "main": "lib/index.js",
6 "scripts": { 6 "scripts": {
@@ -34,5 +34,5 @@
34 "react-dom": "16.7.0", 34 "react-dom": "16.7.0",
35 "react-jss": "^8.6.1" 35 "react-jss": "^8.6.1"
36 }, 36 },
37 "gitHead": "9f2ab40b7602bc3df26ebb093b484b9917768f69" 37 "gitHead": "e1e46986d902adc4c19ee009016290f9733a7d61"
38} 38}
diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx
index 612e23210..5cc41f5b2 100644
--- a/packages/ui/src/badge/ProBadge.tsx
+++ b/packages/ui/src/badge/ProBadge.tsx
@@ -1,15 +1,17 @@
1import { mdiStar } from '@mdi/js';
1import { Theme } from '@meetfranz/theme'; 2import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames'; 3import classnames from 'classnames';
3import React, { Component } from 'react'; 4import React, { Component } from 'react';
4import injectStyle from 'react-jss'; 5import injectStyle from 'react-jss';
5 6
6import { Icon, Badge } from '../'; 7import { Badge, Icon } from '../';
7import { IWithStyle } from '../typings/generic'; 8import { IWithStyle } from '../typings/generic';
8 9
9interface IProps extends IWithStyle { 10interface IProps extends IWithStyle {
10 badgeClasses?: string; 11 badgeClasses?: string;
11 iconClasses?: string; 12 iconClasses?: string;
12 inverted?: boolean; 13 inverted?: boolean;
14 className?: string;
13} 15}
14 16
15const styles = (theme: Theme) => ({ 17const styles = (theme: Theme) => ({
@@ -37,6 +39,7 @@ class ProBadgeComponent extends Component<IProps> {
37 badgeClasses, 39 badgeClasses,
38 iconClasses, 40 iconClasses,
39 inverted, 41 inverted,
42 className,
40 } = this.props; 43 } = this.props;
41 44
42 return ( 45 return (
@@ -46,10 +49,11 @@ class ProBadgeComponent extends Component<IProps> {
46 classes.badge, 49 classes.badge,
47 inverted && classes.invertedBadge, 50 inverted && classes.invertedBadge,
48 badgeClasses, 51 badgeClasses,
52 className,
49 ])} 53 ])}
50 > 54 >
51 <Icon 55 <Icon
52 icon="mdiStar" 56 icon={mdiStar}
53 className={classnames([ 57 className={classnames([
54 classes.icon, 58 classes.icon,
55 inverted && classes.invertedIcon, 59 inverted && classes.invertedIcon,
diff --git a/packages/ui/src/icon/index.tsx b/packages/ui/src/icon/index.tsx
index e30d3396d..af467c085 100644
--- a/packages/ui/src/icon/index.tsx
+++ b/packages/ui/src/icon/index.tsx
@@ -1,4 +1,3 @@
1import * as mdiIcons from '@mdi/js';
2import MdiIcon from '@mdi/react'; 1import MdiIcon from '@mdi/react';
3import { Theme } from '@meetfranz/theme'; 2import { Theme } from '@meetfranz/theme';
4import classnames from 'classnames'; 3import classnames from 'classnames';
@@ -8,7 +7,7 @@ import injectStyle from 'react-jss';
8import { IWithStyle } from '../typings/generic'; 7import { IWithStyle } from '../typings/generic';
9 8
10interface IProps extends IWithStyle { 9interface IProps extends IWithStyle {
11 icon: keyof typeof mdiIcons; 10 icon: string;
12 size?: number; 11 size?: number;
13 className?: string; 12 className?: string;
14} 13}
@@ -27,16 +26,13 @@ class IconComponent extends Component<IProps> {
27 render() { 26 render() {
28 const { 27 const {
29 classes, 28 classes,
30 icon: iconName, 29 icon,
31 size, 30 size,
32 className, 31 className,
33 } = this.props; 32 } = this.props;
34 33
35 let icon = ''; 34 if (!icon) {
36 if (iconName && mdiIcons[iconName]) { 35 console.warn('No Icon specified');
37 icon = mdiIcons[iconName];
38 } else if (iconName && !mdiIcons[iconName]) {
39 console.warn(`Icon '${iconName}' was not found`);
40 } 36 }
41 37
42 return ( 38 return (
diff --git a/packages/ui/src/infobox/index.tsx b/packages/ui/src/infobox/index.tsx
index 9066a623e..e4c2c5a3e 100644
--- a/packages/ui/src/infobox/index.tsx
+++ b/packages/ui/src/infobox/index.tsx
@@ -1,3 +1,4 @@
1import { mdiClose } from '@mdi/js';
1import { Theme } from '@meetfranz/theme'; 2import { Theme } from '@meetfranz/theme';
2import classnames from 'classnames'; 3import classnames from 'classnames';
3import React, { Component } from 'react'; 4import React, { Component } from 'react';
@@ -48,13 +49,13 @@ const styles = (theme: Theme) => ({
48 position: 'relative', 49 position: 'relative',
49 overflow: 'hidden', 50 overflow: 'hidden',
50 height: 'auto', 51 height: 'auto',
52 marginBottom: 30,
51 }, 53 },
52 infobox: { 54 infobox: {
53 alignItems: 'center', 55 alignItems: 'center',
54 borderRadius: theme.borderRadiusSmall, 56 borderRadius: theme.borderRadiusSmall,
55 display: 'flex', 57 display: 'flex',
56 height: 'auto', 58 height: 'auto',
57 marginBottom: 30,
58 padding: '15px 20px', 59 padding: '15px 20px',
59 top: 0, 60 top: 0,
60 transition: 'all 0.5s', 61 transition: 'all 0.5s',
@@ -192,7 +193,7 @@ class InfoboxComponent extends Component<IProps, IState> {
192 onClick={this.dismiss.bind(this)} 193 onClick={this.dismiss.bind(this)}
193 className={classes.close} 194 className={classes.close}
194 > 195 >
195 <Icon icon="mdiClose" /> 196 <Icon icon={mdiClose} />
196 </button> 197 </button>
197 )} 198 )}
198 </div> 199 </div>
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/assets/images/workspaces/teaser_dark.png b/src/assets/images/workspaces/teaser_dark.png
new file mode 100644
index 000000000..5b6d7334b
--- /dev/null
+++ b/src/assets/images/workspaces/teaser_dark.png
Binary files differ
diff --git a/src/assets/images/workspaces/teaser_light.png b/src/assets/images/workspaces/teaser_light.png
new file mode 100644
index 000000000..635af43fa
--- /dev/null
+++ b/src/assets/images/workspaces/teaser_light.png
Binary files differ
diff --git a/src/components/TrialActivationInfoBar.js b/src/components/TrialActivationInfoBar.js
new file mode 100644
index 000000000..acdf51d08
--- /dev/null
+++ b/src/components/TrialActivationInfoBar.js
@@ -0,0 +1,94 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4import ms from 'ms';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7
8import InfoBar from './ui/InfoBar';
9
10const messages = defineMessages({
11 message: {
12 id: 'infobar.trialActivated',
13 defaultMessage: '!!!Your trial was successfully activated. Happy messaging!',
14 },
15});
16
17const styles = {
18 notification: {
19 height: 'auto',
20 position: 'absolute',
21 top: -50,
22 transition: 'top 0.3s',
23 zIndex: 300,
24 width: 'calc(100% - 300px)',
25 },
26 show: {
27 top: 0,
28 },
29};
30
31@injectSheet(styles)
32class TrialActivationInfoBar extends Component {
33 static propTypes = {
34 // eslint-disable-next-line
35 classes: PropTypes.object.isRequired,
36 };
37
38 static contextTypes = {
39 intl: intlShape,
40 };
41
42 state = {
43 showing: false,
44 removed: false,
45 }
46
47 componentDidMount() {
48 setTimeout(() => {
49 this.setState({
50 showing: true,
51 });
52 }, 0);
53
54 setTimeout(() => {
55 this.setState({
56 showing: false,
57 });
58 }, ms('6s'));
59
60 setTimeout(() => {
61 this.setState({
62 removed: true,
63 });
64 }, ms('7s'));
65 }
66
67 render() {
68 const { classes } = this.props;
69 const { showing, removed } = this.state;
70 const { intl } = this.context;
71
72 if (removed) return null;
73
74 return (
75 <div
76 className={classnames({
77 [classes.notification]: true,
78 [classes.show]: showing,
79 })}
80 >
81 <InfoBar
82 type="primary"
83 position="top"
84 sticky
85 >
86 <span className="mdi mdi-information" />
87 {intl.formatMessage(messages.message)}
88 </InfoBar>
89 </div>
90 );
91 }
92}
93
94export default TrialActivationInfoBar;
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index 7ab14f429..cbeaaa5d9 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -1,40 +1,107 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5// import { Link } from 'react-router'; 5import injectSheet from 'react-jss';
6import { H2, Loader } from '@meetfranz/ui';
7import classnames from 'classnames';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureItem } from '../ui/FeatureItem';
11import { FeatureList } from '../ui/FeatureList';
6 12
7// import Button from '../ui/Button';
8import Loader from '../ui/Loader';
9import Appear from '../ui/effects/Appear';
10import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen';
11 13
12const messages = defineMessages({ 14const messages = defineMessages({
13 headline: { 15 headline: {
14 id: 'pricing.headline', 16 id: 'pricing.trial.headline',
15 defaultMessage: '!!!Support Franz', 17 defaultMessage: '!!!Franz Professional',
18 },
19 personalOffer: {
20 id: 'pricing.trial.subheadline',
21 defaultMessage: '!!!Your personal welcome offer:',
22 },
23 noStringsAttachedHeadline: {
24 id: 'pricing.trial.terms.headline',
25 defaultMessage: '!!!No strings attached',
26 },
27 noCreditCard: {
28 id: 'pricing.trial.terms.noCreditCard',
29 defaultMessage: '!!!No credit card required',
16 }, 30 },
17 monthlySupportLabel: { 31 automaticTrialEnd: {
18 id: 'pricing.support.label', 32 id: 'pricing.trial.terms.automaticTrialEnd',
19 defaultMessage: '!!!Select your support plan', 33 defaultMessage: '!!!Your free trial ends automatically after 14 days',
20 }, 34 },
21 submitButtonLabel: { 35 activationError: {
22 id: 'pricing.submit.label', 36 id: 'pricing.trial.error',
23 defaultMessage: '!!!Support the development of Franz', 37 defaultMessage: '!!!Sorry, we could not activate your trial!',
24 }, 38 },
25 skipPayment: { 39 ctaAccept: {
26 id: 'pricing.link.skipPayment', 40 id: 'pricing.trial.cta.accept',
27 defaultMessage: '!!!I don\'t want to support the development of Franz.', 41 defaultMessage: '!!!Yes, upgrade my account to Franz Professional',
42 },
43 ctaSkip: {
44 id: 'pricing.trial.cta.skip',
45 defaultMessage: '!!!Continue to Franz',
46 },
47 featuresHeadline: {
48 id: 'pricing.trial.features.headline',
49 defaultMessage: '!!!Franz Professional includes:',
28 }, 50 },
29}); 51});
30 52
31export default @observer class Signup extends Component { 53const styles = theme => ({
54 container: {
55 position: 'relative',
56 marginLeft: -150,
57 },
58 welcomeOffer: {
59 textAlign: 'center',
60 fontWeight: 'bold',
61 },
62 keyTerms: {
63 textAlign: 'center',
64 },
65 content: {
66 position: 'relative',
67 zIndex: 20,
68 },
69 featureContainer: {
70 width: 300,
71 position: 'absolute',
72 left: 'calc(100% / 2 + 225px)',
73 top: 155,
74 background: theme.signup.pricing.feature.background,
75 height: 'auto',
76 padding: 20,
77 borderTopRightRadius: theme.borderRadius,
78 borderBottomRightRadius: theme.borderRadius,
79 zIndex: 10,
80 },
81 featureItem: {
82 borderBottom: [1, 'solid', theme.signup.pricing.feature.border],
83 },
84 cta: {
85 marginTop: 40,
86 width: '100%',
87 },
88 skipLink: {
89 textAlign: 'center',
90 marginTop: 10,
91 },
92 error: {
93 margin: [20, 0, 0],
94 color: theme.styleTypes.danger.accent,
95 },
96});
97
98export default @observer @injectSheet(styles) class Signup extends Component {
32 static propTypes = { 99 static propTypes = {
33 donor: MobxPropTypes.objectOrObservableObject.isRequired, 100 onSubmit: PropTypes.func.isRequired,
34 isLoading: PropTypes.bool.isRequired, 101 isLoadingRequiredData: PropTypes.bool.isRequired,
35 isLoadingUser: PropTypes.bool.isRequired, 102 isActivatingTrial: PropTypes.bool.isRequired,
36 onCloseSubscriptionWindow: PropTypes.func.isRequired, 103 trialActivationError: PropTypes.bool.isRequired,
37 skipAction: PropTypes.func.isRequired, 104 classes: PropTypes.object.isRequired,
38 }; 105 };
39 106
40 static contextTypes = { 107 static contextTypes = {
@@ -43,70 +110,37 @@ export default @observer class Signup extends Component {
43 110
44 render() { 111 render() {
45 const { 112 const {
46 donor, 113 onSubmit,
47 isLoading, 114 isLoadingRequiredData,
48 isLoadingUser, 115 isActivatingTrial,
49 onCloseSubscriptionWindow, 116 trialActivationError,
50 skipAction, 117 classes,
51 } = this.props; 118 } = this.props;
52 const { intl } = this.context; 119 const { intl } = this.context;
53 120
54 return ( 121 return (
55 <div className="auth__scroll-container"> 122 <div className={classnames('auth__scroll-container', classes.container)}>
56 <div className="auth__container auth__container--signup"> 123 <div className={classnames('auth__container', 'auth__container--signup', classes.content)}>
57 <form className="franz-form auth__form"> 124 <form className="franz-form auth__form">
58 <img 125 {isLoadingRequiredData ? <Loader /> : (
59 src="./assets/images/sm.png" 126 <img
60 className="auth__logo auth__logo--sm" 127 src="./assets/images/sm.png"
61 alt="" 128 className="auth__logo auth__logo--sm"
62 /> 129 alt=""
130 />
131 )}
132 <p className={classes.welcomeOffer}>{intl.formatMessage(messages.personalOffer)}</p>
63 <h1>{intl.formatMessage(messages.headline)}</h1> 133 <h1>{intl.formatMessage(messages.headline)}</h1>
64 <div className="auth__letter"> 134 <div className="auth__letter">
65 {isLoadingUser && ( 135 <p>
66 <p>Loading</p> 136 We built Franz with a lot of effort, manpower and love,
67 )} 137 to boost up your messaging experience.
68 {!isLoadingUser && ( 138 <br />
69 donor.amount ? ( 139 </p>
70 <span> 140 <p>
71 <p> 141 Get the free 14 day Franz Professional trial and see your communication evolving.
72 Thank you so much for your previous donation of 142 <br />
73 {' '} 143 </p>
74 <strong>
75 $
76 {donor.amount}
77 </strong>
78 .
79 <br />
80 Your support allowed us to get where we are today.
81 <br />
82 </p>
83 <p>
84 As an early supporter, you get
85 {' '}
86 <strong>a lifetime premium supporter license</strong>
87 {' '}
88 without any
89 additional charges.
90 </p>
91 <p>
92 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
93 <br />
94 <br />
95 </p>
96 </span>
97 ) : (
98 <span>
99 <p>
100 We built Franz with a lot of effort, manpower and love,
101 to bring you the best messaging experience.
102 <br />
103 </p>
104 <p>
105 Getting a Franz Premium Supporter License will allow us to keep improving Franz for you.
106 </p>
107 </span>
108 )
109 )}
110 <p> 144 <p>
111 Thanks for being a hero. 145 Thanks for being a hero.
112 </p> 146 </p>
@@ -114,20 +148,48 @@ export default @observer class Signup extends Component {
114 <strong>Stefan Malzner</strong> 148 <strong>Stefan Malzner</strong>
115 </p> 149 </p>
116 </div> 150 </div>
117 <Loader loaded={!isLoading}> 151 <div className={classes.keyTerms}>
118 <Appear transitionName="slideDown"> 152 <H2>
119 <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span> 153 {intl.formatMessage(messages.noStringsAttachedHeadline)}
120 <SubscriptionForm 154 </H2>
121 onCloseWindow={onCloseSubscriptionWindow} 155 <ul className={classes.keyTermsList}>
122 showSkipOption 156 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
123 skipAction={skipAction} 157 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
124 hideInfo={Boolean(donor.amount)} 158 </ul>
125 skipButtonLabel={intl.formatMessage(messages.skipPayment)} 159 </div>
126 /> 160 {trialActivationError && (
127 </Appear> 161 <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p>
128 </Loader> 162 )}
163 <Button
164 label={intl.formatMessage(messages.ctaAccept)}
165 className={classes.cta}
166 onClick={onSubmit}
167 busy={isActivatingTrial}
168 disabled={isLoadingRequiredData || isActivatingTrial}
169 />
170 <p className={classes.skipLink}>
171 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a>
172 </p>
129 </form> 173 </form>
130 </div> 174 </div>
175 <div className={classes.featureContainer}>
176 <H2>
177 {intl.formatMessage(messages.featuresHeadline)}
178 </H2>
179 {/* <ul className={classes.features}>
180 <FeatureItem name="Add unlimited services" className={classes.featureItem} />
181 <FeatureItem name="Spellchecker support" className={classes.featureItem} />
182 <FeatureItem name="Workspaces" className={classes.featureItem} />
183 <FeatureItem name="Add Custom Websites" className={classes.featureItem} />
184 <FeatureItem name="On-premise & other Hosted Services" className={classes.featureItem} />
185 <FeatureItem name="Install 3rd party services" className={classes.featureItem} />
186 <FeatureItem name="Service Proxies" className={classes.featureItem} />
187 <FeatureItem name="Team Management" className={classes.featureItem} />
188 <FeatureItem name="No Waiting Screens" className={classes.featureItem} />
189 <FeatureItem name="Forever ad-free" className={classes.featureItem} />
190 </ul> */}
191 <FeatureList />
192 </div>
131 </div> 193 </div>
132 ); 194 );
133 } 195 }
diff --git a/src/components/auth/Signup.js b/src/components/auth/Signup.js
index d9b83eeb8..0499d764b 100644
--- a/src/components/auth/Signup.js
+++ b/src/components/auth/Signup.js
@@ -7,7 +7,6 @@ import { isDevMode, useLiveAPI } from '../../environment';
7import Form from '../../lib/Form'; 7import Form from '../../lib/Form';
8import { required, email, minLength } from '../../helpers/validation-helpers'; 8import { required, email, minLength } from '../../helpers/validation-helpers';
9import Input from '../ui/Input'; 9import Input from '../ui/Input';
10import Radio from '../ui/Radio';
11import Button from '../ui/Button'; 10import Button from '../ui/Button';
12import Link from '../ui/Link'; 11import Link from '../ui/Link';
13import Infobox from '../ui/Infobox'; 12import Infobox from '../ui/Infobox';
@@ -31,10 +30,10 @@ const messages = defineMessages({
31 id: 'signup.email.label', 30 id: 'signup.email.label',
32 defaultMessage: '!!!Email address', 31 defaultMessage: '!!!Email address',
33 }, 32 },
34 companyLabel: { 33 // companyLabel: {
35 id: 'signup.company.label', 34 // id: 'signup.company.label',
36 defaultMessage: '!!!Company', 35 // defaultMessage: '!!!Company',
37 }, 36 // },
38 passwordLabel: { 37 passwordLabel: {
39 id: 'signup.password.label', 38 id: 'signup.password.label',
40 defaultMessage: '!!!Password', 39 defaultMessage: '!!!Password',
@@ -79,20 +78,6 @@ export default @observer class Signup extends Component {
79 78
80 form = new Form({ 79 form = new Form({
81 fields: { 80 fields: {
82 accountType: {
83 value: 'individual',
84 validators: [required],
85 options: [{
86 value: 'individual',
87 label: 'Individual',
88 }, {
89 value: 'non-profit',
90 label: 'Non-Profit',
91 }, {
92 value: 'company',
93 label: 'Company',
94 }],
95 },
96 firstname: { 81 firstname: {
97 label: this.context.intl.formatMessage(messages.firstnameLabel), 82 label: this.context.intl.formatMessage(messages.firstnameLabel),
98 value: '', 83 value: '',
@@ -108,10 +93,6 @@ export default @observer class Signup extends Component {
108 value: '', 93 value: '',
109 validators: [required, email], 94 validators: [required, email],
110 }, 95 },
111 organization: {
112 label: this.context.intl.formatMessage(messages.companyLabel),
113 value: '', // TODO: make required when accountType: company
114 },
115 password: { 96 password: {
116 label: this.context.intl.formatMessage(messages.passwordLabel), 97 label: this.context.intl.formatMessage(messages.passwordLabel),
117 value: '', 98 value: '',
@@ -151,7 +132,6 @@ export default @observer class Signup extends Component {
151 In Dev Mode your data is not persistent. Please use the live app for accesing the production API. 132 In Dev Mode your data is not persistent. Please use the live app for accesing the production API.
152 </Infobox> 133 </Infobox>
153 )} 134 )}
154 <Radio field={form.$('accountType')} showLabel={false} />
155 <div className="grid__row"> 135 <div className="grid__row">
156 <Input field={form.$('firstname')} focus /> 136 <Input field={form.$('firstname')} focus />
157 <Input field={form.$('lastname')} /> 137 <Input field={form.$('lastname')} />
@@ -162,9 +142,6 @@ export default @observer class Signup extends Component {
162 showPasswordToggle 142 showPasswordToggle
163 scorePassword 143 scorePassword
164 /> 144 />
165 {form.$('accountType').value === 'company' && (
166 <Input field={form.$('organization')} />
167 )}
168 {error.code === 'email-duplicate' && ( 145 {error.code === 'email-duplicate' && (
169 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p> 146 <p className="error-message center">{intl.formatMessage(messages.emailDuplicate)}</p>
170 )} 147 )}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index dbf7d3c21..941e60bfd 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -17,6 +17,7 @@ import { isWindows } from '../../environment';
17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; 17import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
18import { workspaceStore } from '../../features/workspaces'; 18import { workspaceStore } from '../../features/workspaces';
19import AppUpdateInfoBar from '../AppUpdateInfoBar'; 19import AppUpdateInfoBar from '../AppUpdateInfoBar';
20import TrialActivationInfoBar from '../TrialActivationInfoBar';
20import Todos from '../../features/todos/containers/TodosScreen'; 21import Todos from '../../features/todos/containers/TodosScreen';
21 22
22function createMarkup(HTMLString) { 23function createMarkup(HTMLString) {
@@ -70,6 +71,7 @@ class AppLayout extends Component {
70 retryRequiredRequests: PropTypes.func.isRequired, 71 retryRequiredRequests: PropTypes.func.isRequired,
71 areRequiredRequestsLoading: PropTypes.bool.isRequired, 72 areRequiredRequestsLoading: PropTypes.bool.isRequired,
72 isDelayAppScreenVisible: PropTypes.bool.isRequired, 73 isDelayAppScreenVisible: PropTypes.bool.isRequired,
74 hasActivatedTrial: PropTypes.bool.isRequired,
73 }; 75 };
74 76
75 static defaultProps = { 77 static defaultProps = {
@@ -89,7 +91,6 @@ class AppLayout extends Component {
89 sidebar, 91 sidebar,
90 services, 92 services,
91 children, 93 children,
92 // isOnline,
93 news, 94 news,
94 showServicesUpdatedInfoBar, 95 showServicesUpdatedInfoBar,
95 appUpdateIsDownloaded, 96 appUpdateIsDownloaded,
@@ -102,6 +103,7 @@ class AppLayout extends Component {
102 retryRequiredRequests, 103 retryRequiredRequests,
103 areRequiredRequestsLoading, 104 areRequiredRequestsLoading,
104 isDelayAppScreenVisible, 105 isDelayAppScreenVisible,
106 hasActivatedTrial,
105 } = this.props; 107 } = this.props;
106 108
107 const { intl } = this.context; 109 const { intl } = this.context;
@@ -126,17 +128,20 @@ class AppLayout extends Component {
126 <span dangerouslySetInnerHTML={createMarkup(item.message)} /> 128 <span dangerouslySetInnerHTML={createMarkup(item.message)} />
127 </InfoBar> 129 </InfoBar>
128 ))} 130 ))}
131 {hasActivatedTrial && (
132 <TrialActivationInfoBar />
133 )}
129 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 134 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
130 <InfoBar 135 <InfoBar
131 type="danger" 136 type="danger"
132 ctaLabel="Try again" 137 ctaLabel="Try again"
133 ctaLoading={areRequiredRequestsLoading} 138 ctaLoading={areRequiredRequestsLoading}
134 sticky 139 sticky
135 onClick={retryRequiredRequests} 140 onClick={retryRequiredRequests}
136 > 141 >
137 <span className="mdi mdi-flash" /> 142 <span className="mdi mdi-flash" />
138 {intl.formatMessage(messages.requiredRequestsFailed)} 143 {intl.formatMessage(messages.requiredRequestsFailed)}
139 </InfoBar> 144 </InfoBar>
140 )} 145 )}
141 {showServicesUpdatedInfoBar && ( 146 {showServicesUpdatedInfoBar && (
142 <InfoBar 147 <InfoBar
diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js
new file mode 100644
index 000000000..4b8d926aa
--- /dev/null
+++ b/src/components/services/content/ServiceRestricted.js
@@ -0,0 +1,78 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { serviceLimitStore } from '../../../features/serviceLimit';
7import Button from '../../ui/Button';
8import { RESTRICTION_TYPES } from '../../../models/Service';
9
10const messages = defineMessages({
11 headlineServiceLimit: {
12 id: 'service.restrictedHandler.serviceLimit.headline',
13 defaultMessage: '!!!You have reached your service limit.',
14 },
15 textServiceLimit: {
16 id: 'service.restrictedHandler.serviceLimit.text',
17 defaultMessage: '!!!Please upgrade your account to use more than {count} services.',
18 },
19 headlineCustomUrl: {
20 id: 'service.restrictedHandler.customUrl.headline',
21 defaultMessage: '!!!Franz Professional Plan required',
22 },
23 textCustomUrl: {
24 id: 'service.restrictedHandler.customUrl.text',
25 defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.',
26 },
27 action: {
28 id: 'service.restrictedHandler.action',
29 defaultMessage: '!!!Upgrade Account',
30 },
31});
32
33export default @observer class ServiceRestricted extends Component {
34 static propTypes = {
35 name: PropTypes.string.isRequired,
36 upgrade: PropTypes.func.isRequired,
37 type: PropTypes.number.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 countdownInterval = null;
45
46 countdownIntervalTimeout = 1000;
47
48 render() {
49 const {
50 name,
51 upgrade,
52 type,
53 } = this.props;
54 const { intl } = this.context;
55
56 return (
57 <div className="services__info-layer">
58 {type === RESTRICTION_TYPES.SERVICE_LIMIT && (
59 <>
60 <h1>{intl.formatMessage(messages.headlineServiceLimit)}</h1>
61 <p>{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}</p>
62 </>
63 )}
64 {type === RESTRICTION_TYPES.CUSTOM_URL && (
65 <>
66 <h1>{intl.formatMessage(messages.headlineCustomUrl)}</h1>
67 <p>{intl.formatMessage(messages.textCustomUrl)}</p>
68 </>
69 )}
70 <Button
71 label={intl.formatMessage(messages.action, { name })}
72 buttonType="inverted"
73 onClick={() => upgrade()}
74 />
75 </div>
76 );
77 }
78}
diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js
index 13148b9b3..f65f51346 100644
--- a/src/components/services/content/ServiceView.js
+++ b/src/components/services/content/ServiceView.js
@@ -10,6 +10,7 @@ import WebviewLoader from '../../ui/WebviewLoader';
10import WebviewCrashHandler from './WebviewCrashHandler'; 10import WebviewCrashHandler from './WebviewCrashHandler';
11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; 11import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler';
12import ServiceDisabled from './ServiceDisabled'; 12import ServiceDisabled from './ServiceDisabled';
13import ServiceRestricted from './ServiceRestricted';
13import ServiceWebview from './ServiceWebview'; 14import ServiceWebview from './ServiceWebview';
14 15
15export default @observer class ServiceView extends Component { 16export default @observer class ServiceView extends Component {
@@ -21,6 +22,7 @@ export default @observer class ServiceView extends Component {
21 edit: PropTypes.func.isRequired, 22 edit: PropTypes.func.isRequired,
22 enable: PropTypes.func.isRequired, 23 enable: PropTypes.func.isRequired,
23 isActive: PropTypes.bool, 24 isActive: PropTypes.bool,
25 upgrade: PropTypes.func.isRequired,
24 }; 26 };
25 27
26 static defaultProps = { 28 static defaultProps = {
@@ -72,6 +74,7 @@ export default @observer class ServiceView extends Component {
72 reload, 74 reload,
73 edit, 75 edit,
74 enable, 76 enable,
77 upgrade,
75 } = this.props; 78 } = this.props;
76 79
77 const webviewClasses = classnames({ 80 const webviewClasses = classnames({
@@ -99,7 +102,7 @@ export default @observer class ServiceView extends Component {
99 reload={reload} 102 reload={reload}
100 /> 103 />
101 )} 104 )}
102 {service.isEnabled && service.isLoading && service.isFirstLoad && ( 105 {service.isEnabled && service.isLoading && service.isFirstLoad && !service.isServiceAccessRestricted && (
103 <WebviewLoader 106 <WebviewLoader
104 loaded={false} 107 loaded={false}
105 name={service.name} 108 name={service.name}
@@ -126,11 +129,21 @@ export default @observer class ServiceView extends Component {
126 )} 129 )}
127 </Fragment> 130 </Fragment>
128 ) : ( 131 ) : (
129 <ServiceWebview 132 <>
130 service={service} 133 {service.isServiceAccessRestricted ? (
131 setWebviewReference={setWebviewReference} 134 <ServiceRestricted
132 detachService={detachService} 135 name={service.recipe.name}
133 /> 136 upgrade={upgrade}
137 type={service.restrictionType}
138 />
139 ) : (
140 <ServiceWebview
141 service={service}
142 setWebviewReference={setWebviewReference}
143 detachService={detachService}
144 />
145 )}
146 </>
134 )} 147 )}
135 {statusBar} 148 {statusBar}
136 </div> 149 </div>
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 8f8c38a11..73c27bfb6 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, intlShape } from 'react-intl';
6import Confetti from 'react-confetti';
7import ms from 'ms';
8import injectSheet from 'react-jss';
6 9
7import ServiceView from './ServiceView'; 10import ServiceView from './ServiceView';
8import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
@@ -18,7 +21,17 @@ const messages = defineMessages({
18 }, 21 },
19}); 22});
20 23
21export default @observer class Services extends Component { 24
25const styles = {
26 confettiContainer: {
27 position: 'absolute',
28 width: '100%',
29 zIndex: 9999,
30 pointerEvents: 'none',
31 },
32};
33
34export default @observer @injectSheet(styles) class Services extends Component {
22 static propTypes = { 35 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray, 36 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 37 setWebviewReference: PropTypes.func.isRequired,
@@ -28,6 +41,9 @@ export default @observer class Services extends Component {
28 reload: PropTypes.func.isRequired, 41 reload: PropTypes.func.isRequired,
29 openSettings: PropTypes.func.isRequired, 42 openSettings: PropTypes.func.isRequired,
30 update: PropTypes.func.isRequired, 43 update: PropTypes.func.isRequired,
44 userHasCompletedSignup: PropTypes.bool.isRequired,
45 hasActivatedTrial: PropTypes.bool.isRequired,
46 classes: PropTypes.object.isRequired,
31 }; 47 };
32 48
33 static defaultProps = { 49 static defaultProps = {
@@ -38,6 +54,18 @@ export default @observer class Services extends Component {
38 intl: intlShape, 54 intl: intlShape,
39 }; 55 };
40 56
57 state = {
58 showConfetti: true,
59 }
60
61 componentDidMount() {
62 window.setTimeout(() => {
63 this.setState({
64 showConfetti: false,
65 });
66 }, ms('8s'));
67 }
68
41 render() { 69 render() {
42 const { 70 const {
43 services, 71 services,
@@ -48,11 +76,28 @@ export default @observer class Services extends Component {
48 reload, 76 reload,
49 openSettings, 77 openSettings,
50 update, 78 update,
79 userHasCompletedSignup,
80 hasActivatedTrial,
81 classes,
51 } = this.props; 82 } = this.props;
83
84 const {
85 showConfetti,
86 } = this.state;
87
52 const { intl } = this.context; 88 const { intl } = this.context;
53 89
54 return ( 90 return (
55 <div className="services"> 91 <div className="services">
92 {(userHasCompletedSignup || hasActivatedTrial) && (
93 <div className={classes.confettiContainer}>
94 <Confetti
95 width={window.width}
96 height={window.height}
97 numberOfPieces={showConfetti ? 200 : 0}
98 />
99 </div>
100 )}
56 {services.length === 0 && ( 101 {services.length === 0 && (
57 <Appear 102 <Appear
58 timeout={1500} 103 timeout={1500}
@@ -89,6 +134,7 @@ export default @observer class Services extends Component {
89 }, 134 },
90 redirect: false, 135 redirect: false,
91 })} 136 })}
137 upgrade={() => openSettings({ path: 'user' })}
92 /> 138 />
93 ))} 139 ))}
94 </div> 140 </div>
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 3f6964b6b..4fd1e8163 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -1,14 +1,18 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { ProBadge } from '@meetfranz/ui'; 6import {
7 ProBadge, H1, H2,
8} from '@meetfranz/ui';
9import moment from 'moment';
7 10
8import Loader from '../../ui/Loader'; 11import Loader from '../../ui/Loader';
9import Button from '../../ui/Button'; 12import Button from '../../ui/Button';
10import Infobox from '../../ui/Infobox'; 13import Infobox from '../../ui/Infobox';
11import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; 14import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
15import { i18nPlanName } from '../../../helpers/plan-helpers';
12 16
13const messages = defineMessages({ 17const messages = defineMessages({
14 headline: { 18 headline: {
@@ -19,10 +23,6 @@ const messages = defineMessages({
19 id: 'settings.account.headlineSubscription', 23 id: 'settings.account.headlineSubscription',
20 defaultMessage: '!!!Your Subscription', 24 defaultMessage: '!!!Your Subscription',
21 }, 25 },
22 headlineUpgrade: {
23 id: 'settings.account.headlineUpgrade',
24 defaultMessage: '!!!Upgrade your Account',
25 },
26 headlineDangerZone: { 26 headlineDangerZone: {
27 id: 'settings.account.headlineDangerZone', 27 id: 'settings.account.headlineDangerZone',
28 defaultMessage: '!!Danger Zone', 28 defaultMessage: '!!Danger Zone',
@@ -31,6 +31,10 @@ const messages = defineMessages({
31 id: 'settings.account.manageSubscription.label', 31 id: 'settings.account.manageSubscription.label',
32 defaultMessage: '!!!Manage your subscription', 32 defaultMessage: '!!!Manage your subscription',
33 }, 33 },
34 upgradeAccountToPro: {
35 id: 'settings.account.upgradeToPro.label',
36 defaultMessage: '!!!Upgrade to Franz Professional',
37 },
34 accountTypeBasic: { 38 accountTypeBasic: {
35 id: 'settings.account.accountType.basic', 39 id: 'settings.account.accountType.basic',
36 defaultMessage: '!!!Basic Account', 40 defaultMessage: '!!!Basic Account',
@@ -71,21 +75,38 @@ const messages = defineMessages({
71 id: 'settings.account.deleteEmailSent', 75 id: 'settings.account.deleteEmailSent',
72 defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 76 defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
73 }, 77 },
78 trial: {
79 id: 'settings.account.trial',
80 defaultMessage: '!!!Free Trial',
81 },
82 yourLicense: {
83 id: 'settings.account.yourLicense',
84 defaultMessage: '!!!Your Franz License:',
85 },
86 trialEndsIn: {
87 id: 'settings.account.trialEndsIn',
88 defaultMessage: '!!!Your free trial ends in {duration}.',
89 },
90 trialUpdateBillingInformation: {
91 id: 'settings.account.trialUpdateBillingInfo',
92 defaultMessage: '!!!Please update your billing info to continue using {license} after your trial period.',
93 },
74}); 94});
75 95
76export default @observer class AccountDashboard extends Component { 96@observer
97class AccountDashboard extends Component {
77 static propTypes = { 98 static propTypes = {
78 user: MobxPropTypes.observableObject.isRequired, 99 user: MobxPropTypes.observableObject.isRequired,
100 isProUser: PropTypes.bool.isRequired,
79 isLoading: PropTypes.bool.isRequired, 101 isLoading: PropTypes.bool.isRequired,
80 isLoadingPlans: PropTypes.bool.isRequired,
81 userInfoRequestFailed: PropTypes.bool.isRequired, 102 userInfoRequestFailed: PropTypes.bool.isRequired,
82 retryUserInfoRequest: PropTypes.func.isRequired, 103 retryUserInfoRequest: PropTypes.func.isRequired,
83 onCloseSubscriptionWindow: PropTypes.func.isRequired,
84 deleteAccount: PropTypes.func.isRequired, 104 deleteAccount: PropTypes.func.isRequired,
85 isLoadingDeleteAccount: PropTypes.bool.isRequired, 105 isLoadingDeleteAccount: PropTypes.bool.isRequired,
86 isDeleteAccountSuccessful: PropTypes.bool.isRequired, 106 isDeleteAccountSuccessful: PropTypes.bool.isRequired,
87 openEditAccount: PropTypes.func.isRequired, 107 openEditAccount: PropTypes.func.isRequired,
88 openBilling: PropTypes.func.isRequired, 108 openBilling: PropTypes.func.isRequired,
109 upgradeToPro: PropTypes.func.isRequired,
89 openInvoices: PropTypes.func.isRequired, 110 openInvoices: PropTypes.func.isRequired,
90 }; 111 };
91 112
@@ -96,20 +117,26 @@ export default @observer class AccountDashboard extends Component {
96 render() { 117 render() {
97 const { 118 const {
98 user, 119 user,
120 isProUser,
99 isLoading, 121 isLoading,
100 isLoadingPlans,
101 userInfoRequestFailed, 122 userInfoRequestFailed,
102 retryUserInfoRequest, 123 retryUserInfoRequest,
103 onCloseSubscriptionWindow,
104 deleteAccount, 124 deleteAccount,
105 isLoadingDeleteAccount, 125 isLoadingDeleteAccount,
106 isDeleteAccountSuccessful, 126 isDeleteAccountSuccessful,
107 openEditAccount, 127 openEditAccount,
108 openBilling, 128 openBilling,
129 upgradeToPro,
109 openInvoices, 130 openInvoices,
110 } = this.props; 131 } = this.props;
111 const { intl } = this.context; 132 const { intl } = this.context;
112 133
134 let planName = '';
135
136 if (user.team && user.team.plan) {
137 planName = i18nPlanName(user.team.plan, intl);
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,115 @@ 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 </>
184 )}
185 </H1>
186 <p>
187 {user.organization && `${user.organization}, `}
188 {user.email}
189 </p>
151 {user.isPremium && ( 190 {user.isPremium && (
191 <div className="manage-user-links">
192 <Button
193 label={intl.formatMessage(messages.accountEditButton)}
194 className="franz-form__button--inverted"
195 onClick={openEditAccount}
196 />
197 </div>
198 )}
199 </div>
200 {!user.isPremium && (
201 <Button
202 label={intl.formatMessage(messages.accountEditButton)}
203 className="franz-form__button--inverted"
204 onClick={openEditAccount}
205 />
206 )}
207 </div>
208 </div>
209 {user.isPremium && user.isSubscriptionOwner && (
210 <div className="account">
211 <div className="account__box">
212 <H2>
213 {intl.formatMessage(messages.yourLicense)}
214 </H2>
215 <p>
216 {planName}
217 {user.team.isTrial && (
218 <>
219 {' – '}
220 {intl.formatMessage(messages.trial)}
221 </>
222 )}
223 </p>
224 {user.team.isTrial && (
152 <> 225 <>
153 {' '} 226 <br />
154 <ProBadge /> 227 <p>
155 <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> 228 {intl.formatMessage(messages.trialEndsIn, {
229 duration: moment.duration(moment().diff(user.team.trialEnd)).humanize(),
230 })}
231 </p>
232 <p>
233 {intl.formatMessage(messages.trialUpdateBillingInformation, {
234 license: planName,
235 })}
236 </p>
156 </> 237 </>
157 )} 238 )}
158 </h2>
159 {user.organization && `${user.organization}, `}
160 {user.email}
161 {user.isPremium && (
162 <div className="manage-user-links"> 239 <div className="manage-user-links">
240 {!isProUser && (
241 <Button
242 label={intl.formatMessage(messages.upgradeAccountToPro)}
243 className="franz-form__button--primary"
244 onClick={upgradeToPro}
245 />
246 )}
163 <Button 247 <Button
164 label={intl.formatMessage(messages.accountEditButton)} 248 label={intl.formatMessage(messages.manageSubscriptionButtonLabel)}
165 className="franz-form__button--inverted" 249 className="franz-form__button--inverted"
166 onClick={openEditAccount} 250 onClick={openBilling}
251 />
252 <Button
253 label={intl.formatMessage(messages.invoicesButton)}
254 className="franz-form__button--inverted"
255 onClick={openInvoices}
167 /> 256 />
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> 257 </div>
183 )} 258 </div>
184 </div> 259 </div>
185 {!user.isPremium && ( 260 )}
186 <Button 261 {!user.isPremium && (
187 label={intl.formatMessage(messages.accountEditButton)} 262 <div className="account franz-form">
188 className="franz-form__button--inverted" 263 <div className="account__box">
189 onClick={openEditAccount} 264 <SubscriptionForm />
190 /> 265 </div>
191 )}
192 </div>
193 </div>
194 )}
195
196 {!user.isPremium && (
197 isLoadingPlans ? (
198 <Loader />
199 ) : (
200 <div className="account franz-form">
201 <div className="account__box">
202 <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2>
203 <SubscriptionForm
204 onCloseWindow={onCloseSubscriptionWindow}
205 />
206 </div> 266 </div>
207 </div> 267 )}
208 ) 268 </>
209 )} 269 )}
210 270
211 <div className="account franz-form"> 271 <div className="account franz-form">
212 <div className="account__box"> 272 <div className="account__box">
213 <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> 273 <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2>
214 {!isDeleteAccountSuccessful && ( 274 {!isDeleteAccountSuccessful && (
215 <div className="account__subscription"> 275 <div className="account__subscription">
216 <p>{intl.formatMessage(messages.deleteInfo)}</p> 276 <p>{intl.formatMessage(messages.deleteInfo)}</p>
@@ -227,7 +287,7 @@ export default @observer class AccountDashboard extends Component {
227 )} 287 )}
228 </div> 288 </div>
229 </div> 289 </div>
230 </Fragment> 290 </>
231 )} 291 )}
232 </div> 292 </div>
233 <ReactTooltip place="right" type="dark" effect="solid" /> 293 <ReactTooltip place="right" type="dark" effect="solid" />
@@ -235,3 +295,5 @@ export default @observer class AccountDashboard extends Component {
235 ); 295 );
236 } 296 }
237} 297}
298
299export default AccountDashboard;
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..08988024a 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -4,12 +4,17 @@ import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import { Link } from 'react-router'; 5import { Link } from 'react-router';
6 6
7import { Button, Input } from '@meetfranz/forms';
8import injectSheet from 'react-jss';
9import { H3, H2, ProBadge } from '@meetfranz/ui';
7import SearchInput from '../../ui/SearchInput'; 10import SearchInput from '../../ui/SearchInput';
8import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
9import RecipeItem from './RecipeItem'; 12import RecipeItem from './RecipeItem';
10import Loader from '../../ui/Loader'; 13import Loader from '../../ui/Loader';
11import Appear from '../../ui/effects/Appear'; 14import Appear from '../../ui/effects/Appear';
12import { FRANZ_SERVICE_REQUEST } from '../../../config'; 15import { FRANZ_SERVICE_REQUEST } from '../../../config';
16import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
17import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
13 18
14const messages = defineMessages({ 19const messages = defineMessages({
15 headline: { 20 headline: {
@@ -28,9 +33,9 @@ const messages = defineMessages({
28 id: 'settings.recipes.all', 33 id: 'settings.recipes.all',
29 defaultMessage: '!!!All services', 34 defaultMessage: '!!!All services',
30 }, 35 },
31 devRecipes: { 36 customRecipes: {
32 id: 'settings.recipes.dev', 37 id: 'settings.recipes.custom',
33 defaultMessage: '!!!Development', 38 defaultMessage: '!!!Custom Services',
34 }, 39 },
35 nothingFound: { 40 nothingFound: {
36 id: 'settings.recipes.nothingFound', 41 id: 'settings.recipes.nothingFound',
@@ -44,9 +49,61 @@ const messages = defineMessages({
44 id: 'settings.recipes.missingService', 49 id: 'settings.recipes.missingService',
45 defaultMessage: '!!!Missing a service?', 50 defaultMessage: '!!!Missing a service?',
46 }, 51 },
52 customRecipeIntro: {
53 id: 'settings.recipes.customService.intro',
54 defaultMessage: '!!!To add a custom service, copy the recipe folder into:',
55 },
56 openFolder: {
57 id: 'settings.recipes.customService.openFolder',
58 defaultMessage: '!!!Open directory',
59 },
60 openDevDocs: {
61 id: 'settings.recipes.customService.openDevDocs',
62 defaultMessage: '!!!Developer Documentation',
63 },
64 headlineCustomRecipes: {
65 id: 'settings.recipes.customService.headline.customRecipes',
66 defaultMessage: '!!!Custom Service Recipes',
67 },
68 headlineCommunityRecipes: {
69 id: 'settings.recipes.customService.headline.communityRecipes',
70 defaultMessage: '!!!Community Services',
71 },
72 headlineDevRecipes: {
73 id: 'settings.recipes.customService.headline.devRecipes',
74 defaultMessage: '!!!Your Development Service Recipes',
75 },
47}); 76});
48 77
49export default @observer class RecipesDashboard extends Component { 78const styles = {
79 devRecipeIntroContainer: {
80 textAlign: 'center',
81 width: '100%',
82 height: 'auto',
83 margin: [40, 0],
84 },
85 path: {
86 marginTop: 20,
87
88 '& > div': {
89 fontFamily: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace',
90 },
91 },
92 actionContainer: {
93 '& button': {
94 margin: [0, 10],
95 },
96 },
97 devRecipeList: {
98 marginTop: 20,
99 height: 'auto',
100 },
101 proBadge: {
102 marginLeft: '10px !important',
103 },
104};
105
106export default @injectSheet(styles) @observer class RecipesDashboard extends Component {
50 static propTypes = { 107 static propTypes = {
51 recipes: MobxPropTypes.arrayOrObservableArray.isRequired, 108 recipes: MobxPropTypes.arrayOrObservableArray.isRequired,
52 isLoading: PropTypes.bool.isRequired, 109 isLoading: PropTypes.bool.isRequired,
@@ -55,12 +112,18 @@ export default @observer class RecipesDashboard extends Component {
55 searchRecipes: PropTypes.func.isRequired, 112 searchRecipes: PropTypes.func.isRequired,
56 resetSearch: PropTypes.func.isRequired, 113 resetSearch: PropTypes.func.isRequired,
57 serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired, 114 serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired,
58 devRecipesCount: PropTypes.number.isRequired,
59 searchNeedle: PropTypes.string, 115 searchNeedle: PropTypes.string,
116 recipeFilter: PropTypes.string,
117 recipeDirectory: PropTypes.string.isRequired,
118 openRecipeDirectory: PropTypes.func.isRequired,
119 openDevDocs: PropTypes.func.isRequired,
120 classes: PropTypes.object.isRequired,
121 isCommunityRecipesIncludedInCurrentPlan: PropTypes.bool.isRequired,
60 }; 122 };
61 123
62 static defaultProps = { 124 static defaultProps = {
63 searchNeedle: '', 125 searchNeedle: '',
126 recipeFilter: 'all',
64 } 127 }
65 128
66 static contextTypes = { 129 static contextTypes = {
@@ -76,16 +139,26 @@ export default @observer class RecipesDashboard extends Component {
76 searchRecipes, 139 searchRecipes,
77 resetSearch, 140 resetSearch,
78 serviceStatus, 141 serviceStatus,
79 devRecipesCount,
80 searchNeedle, 142 searchNeedle,
143 recipeFilter,
144 recipeDirectory,
145 openRecipeDirectory,
146 openDevDocs,
147 classes,
148 isCommunityRecipesIncludedInCurrentPlan,
81 } = this.props; 149 } = this.props;
82 const { intl } = this.context; 150 const { intl } = this.context;
83 151
152
153 const communityRecipes = recipes.filter(r => !r.isDevRecipe);
154 const devRecipes = recipes.filter(r => r.isDevRecipe);
155
84 return ( 156 return (
85 <div className="settings__main"> 157 <div className="settings__main">
86 <div className="settings__header"> 158 <div className="settings__header">
87 <h1>{intl.formatMessage(messages.headline)}</h1> 159 <h1>{intl.formatMessage(messages.headline)}</h1>
88 </div> 160 </div>
161 <LimitReachedInfobox />
89 <div className="settings__body recipes"> 162 <div className="settings__body recipes">
90 {serviceStatus.length > 0 && serviceStatus.includes('created') && ( 163 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
91 <Appear> 164 <Appear>
@@ -122,20 +195,14 @@ export default @observer class RecipesDashboard extends Component {
122 > 195 >
123 {intl.formatMessage(messages.allRecipes)} 196 {intl.formatMessage(messages.allRecipes)}
124 </Link> 197 </Link>
125 {devRecipesCount > 0 && ( 198 <Link
126 <Link 199 to="/settings/recipes/dev"
127 to="/settings/recipes/dev" 200 className="badge"
128 className="badge" 201 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`}
129 activeClassName={`${!searchNeedle ? 'badge--primary' : ''}`} 202 onClick={() => resetSearch()}
130 onClick={() => resetSearch()} 203 >
131 > 204 {intl.formatMessage(messages.customRecipes)}
132 {intl.formatMessage(messages.devRecipes)} 205 </Link>
133 {' '}
134(
135 {devRecipesCount}
136)
137 </Link>
138 )}
139 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request"> 206 <a href={FRANZ_SERVICE_REQUEST} target="_blank" className="link recipes__service-request">
140 {intl.formatMessage(messages.missingService)} 207 {intl.formatMessage(messages.missingService)}
141 {' '} 208 {' '}
@@ -146,23 +213,78 @@ export default @observer class RecipesDashboard extends Component {
146 {isLoading ? ( 213 {isLoading ? (
147 <Loader /> 214 <Loader />
148 ) : ( 215 ) : (
149 <div className="recipes__list"> 216 <>
150 {hasLoadedRecipes && recipes.length === 0 && ( 217 {recipeFilter === 'dev' && (
151 <p className="align-middle settings__empty-state"> 218 <>
152 <span className="emoji"> 219 <H2>
153 <img src="./assets/images/emoji/dontknow.png" alt="" /> 220 {intl.formatMessage(messages.headlineCustomRecipes)}
154 </span> 221 {!isCommunityRecipesIncludedInCurrentPlan && (
155 {intl.formatMessage(messages.nothingFound)} 222 <ProBadge className={classes.proBadge} />
156 </p> 223 )}
224 </H2>
225 <div className={classes.devRecipeIntroContainer}>
226 <p>
227 {intl.formatMessage(messages.customRecipeIntro)}
228 </p>
229 <Input
230 value={recipeDirectory}
231 className={classes.path}
232 showLabel={false}
233 />
234 <div className={classes.actionContainer}>
235 <Button
236 onClick={openRecipeDirectory}
237 buttonType="secondary"
238 label={intl.formatMessage(messages.openFolder)}
239 />
240 <Button
241 onClick={openDevDocs}
242 buttonType="secondary"
243 label={intl.formatMessage(messages.openDevDocs)}
244 />
245 </div>
246 </div>
247 </>
248 )}
249 <PremiumFeatureContainer
250 condition={(recipeFilter === 'dev' && communityRecipes.length > 0) && !isCommunityRecipesIncludedInCurrentPlan}
251 >
252 {recipeFilter === 'dev' && communityRecipes.length > 0 && (
253 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3>
254 )}
255 <div className="recipes__list">
256 {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && (
257 <p className="align-middle settings__empty-state">
258 <span className="emoji">
259 <img src="./assets/images/emoji/dontknow.png" alt="" />
260 </span>
261 {intl.formatMessage(messages.nothingFound)}
262 </p>
263 )}
264 {communityRecipes.map(recipe => (
265 <RecipeItem
266 key={recipe.id}
267 recipe={recipe}
268 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
269 />
270 ))}
271 </div>
272 </PremiumFeatureContainer>
273 {recipeFilter === 'dev' && devRecipes.length > 0 && (
274 <div className={classes.devRecipeList}>
275 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3>
276 <div className="recipes__list">
277 {devRecipes.map(recipe => (
278 <RecipeItem
279 key={recipe.id}
280 recipe={recipe}
281 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
282 />
283 ))}
284 </div>
285 </div>
157 )} 286 )}
158 {recipes.map(recipe => ( 287 </>
159 <RecipeItem
160 key={recipe.id}
161 recipe={recipe}
162 onClick={() => showAddServiceInterface({ recipeId: recipe.id })}
163 />
164 ))}
165 </div>
166 )} 288 )}
167 </div> 289 </div>
168 </div> 290 </div>
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 4ba2eb844..5cde0db8e 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -17,6 +17,8 @@ import ImageUpload from '../../ui/ImageUpload';
17import Select from '../../ui/Select'; 17import Select from '../../ui/Select';
18 18
19import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; 19import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
20import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
21import { serviceLimitStore } from '../../../features/serviceLimit';
20 22
21const messages = defineMessages({ 23const messages = defineMessages({
22 saveService: { 24 saveService: {
@@ -128,8 +130,8 @@ export default @observer class EditServiceForm extends Component {
128 isSaving: PropTypes.bool.isRequired, 130 isSaving: PropTypes.bool.isRequired,
129 isDeleting: PropTypes.bool.isRequired, 131 isDeleting: PropTypes.bool.isRequired,
130 isProxyFeatureEnabled: PropTypes.bool.isRequired, 132 isProxyFeatureEnabled: PropTypes.bool.isRequired,
131 isProxyPremiumFeature: PropTypes.bool.isRequired, 133 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired,
132 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, 134 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
133 }; 135 };
134 136
135 static defaultProps = { 137 static defaultProps = {
@@ -192,8 +194,8 @@ export default @observer class EditServiceForm extends Component {
192 isDeleting, 194 isDeleting,
193 onDelete, 195 onDelete,
194 isProxyFeatureEnabled, 196 isProxyFeatureEnabled,
195 isProxyPremiumFeature, 197 isServiceProxyIncludedInCurrentPlan,
196 isSpellcheckerPremiumFeature, 198 isSpellcheckerIncludedInCurrentPlan,
197 } = this.props; 199 } = this.props;
198 const { intl } = this.context; 200 const { intl } = this.context;
199 201
@@ -252,6 +254,7 @@ export default @observer class EditServiceForm extends Component {
252 )} 254 )}
253 </span> 255 </span>
254 </div> 256 </div>
257 <LimitReachedInfobox />
255 <div className="settings__body"> 258 <div className="settings__body">
256 <form onSubmit={e => this.submit(e)} id="form"> 259 <form onSubmit={e => this.submit(e)} id="form">
257 <div className="service-name"> 260 <div className="service-name">
@@ -342,7 +345,7 @@ export default @observer class EditServiceForm extends Component {
342 </div> 345 </div>
343 346
344 <PremiumFeatureContainer 347 <PremiumFeatureContainer
345 condition={isSpellcheckerPremiumFeature} 348 condition={!isSpellcheckerIncludedInCurrentPlan}
346 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 349 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
347 > 350 >
348 <div className="settings__settings-group"> 351 <div className="settings__settings-group">
@@ -352,7 +355,7 @@ export default @observer class EditServiceForm extends Component {
352 355
353 {isProxyFeatureEnabled && ( 356 {isProxyFeatureEnabled && (
354 <PremiumFeatureContainer 357 <PremiumFeatureContainer
355 condition={isProxyPremiumFeature} 358 condition={!isServiceProxyIncludedInCurrentPlan}
356 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }} 359 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }}
357 > 360 >
358 <div className="settings__settings-group"> 361 <div className="settings__settings-group">
@@ -418,7 +421,7 @@ export default @observer class EditServiceForm extends Component {
418 type="submit" 421 type="submit"
419 label={intl.formatMessage(messages.saveService)} 422 label={intl.formatMessage(messages.saveService)}
420 htmlForm="form" 423 htmlForm="form"
421 disabled={action !== 'edit' && form.isPristine && requiresUserInput} 424 disabled={action !== 'edit' && ((form.isPristine && requiresUserInput) || serviceLimitStore.userHasReachedServiceLimit)}
422 /> 425 />
423 )} 426 )}
424 </div> 427 </div>
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index 53bae12df..78038e86a 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -9,6 +9,7 @@ import Infobox from '../../ui/Infobox';
9import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
10import ServiceItem from './ServiceItem'; 10import ServiceItem from './ServiceItem';
11import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
12import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 headline: { 15 headline: {
@@ -91,6 +92,7 @@ export default @observer class ServicesDashboard extends Component {
91 <div className="settings__header"> 92 <div className="settings__header">
92 <h1>{intl.formatMessage(messages.headline)}</h1> 93 <h1>{intl.formatMessage(messages.headline)}</h1>
93 </div> 94 </div>
95 <LimitReachedInfobox />
94 <div className="settings__body"> 96 <div className="settings__body">
95 {!isLoading && ( 97 {!isLoading && (
96 <SearchInput 98 <SearchInput
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index efd453356..3f9e0a6bc 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -100,7 +100,7 @@ export default @observer class EditSettingsForm extends Component {
100 isClearingAllCache: PropTypes.bool.isRequired, 100 isClearingAllCache: PropTypes.bool.isRequired,
101 onClearAllCache: PropTypes.func.isRequired, 101 onClearAllCache: PropTypes.func.isRequired,
102 cacheSize: PropTypes.string.isRequired, 102 cacheSize: PropTypes.string.isRequired,
103 isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, 103 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
104 }; 104 };
105 105
106 static contextTypes = { 106 static contextTypes = {
@@ -130,7 +130,7 @@ export default @observer class EditSettingsForm extends Component {
130 isClearingAllCache, 130 isClearingAllCache,
131 onClearAllCache, 131 onClearAllCache,
132 cacheSize, 132 cacheSize,
133 isSpellcheckerPremiumFeature, 133 isSpellcheckerIncludedInCurrentPlan,
134 } = this.props; 134 } = this.props;
135 const { intl } = this.context; 135 const { intl } = this.context;
136 136
@@ -173,7 +173,7 @@ export default @observer class EditSettingsForm extends Component {
173 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2> 173 <h2 id="language">{intl.formatMessage(messages.headlineLanguage)}</h2>
174 <Select field={form.$('locale')} showLabel={false} /> 174 <Select field={form.$('locale')} showLabel={false} />
175 <PremiumFeatureContainer 175 <PremiumFeatureContainer
176 condition={isSpellcheckerPremiumFeature} 176 condition={!isSpellcheckerIncludedInCurrentPlan}
177 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 177 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }}
178 > 178 >
179 <Fragment> 179 <Fragment>
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 82c517fcb..990ee52e7 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -133,13 +133,13 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
133 </div> 133 </div>
134 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" /> 134 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" />
135 </div> 135 </div>
136 <Button
137 label={intl.formatMessage(messages.manageButton)}
138 onClick={openTeamManagement}
139 className={classes.cta}
140 />
141 </> 136 </>
142 </PremiumFeatureContainer> 137 </PremiumFeatureContainer>
138 <Button
139 label={intl.formatMessage(messages.manageButton)}
140 onClick={openTeamManagement}
141 className={classes.cta}
142 />
143 </> 143 </>
144 )} 144 )}
145 </> 145 </>
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
index 50f1e0522..cdfbbe60d 100644
--- a/src/components/subscription/SubscriptionForm.js
+++ b/src/components/subscription/SubscriptionForm.js
@@ -1,214 +1,78 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
5 6
6import Form from '../../lib/Form'; 7import { H3, H2 } from '@meetfranz/ui';
7import Radio from '../ui/Radio';
8import Button from '../ui/Button';
9import Loader from '../ui/Loader';
10 8
11import { required } from '../../helpers/validation-helpers'; 9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
12 11
13const messages = defineMessages({ 12const messages = defineMessages({
14 submitButtonLabel: { 13 submitButtonLabel: {
15 id: 'subscription.submit.label', 14 id: 'subscription.cta.choosePlan',
16 defaultMessage: '!!!Support the development of Franz', 15 defaultMessage: '!!!Choose your plan',
17 }, 16 },
18 paymentSessionError: { 17 teaserHeadline: {
19 id: 'subscription.paymentSessionError', 18 id: 'settings.account.headlineUpgradeAccount',
20 defaultMessage: '!!!Could not initialize payment form', 19 defaultMessage: '!!!Upgrade your account and get the full Franz experience',
21 }, 20 },
22 typeFree: { 21 teaserText: {
23 id: 'subscription.type.free', 22 id: 'subscription.teaser.intro',
24 defaultMessage: '!!!free', 23 defaultMessage: '!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!',
25 },
26 typeMonthly: {
27 id: 'subscription.type.month',
28 defaultMessage: '!!!month',
29 },
30 typeYearly: {
31 id: 'subscription.type.year',
32 defaultMessage: '!!!year',
33 }, 24 },
34 includedFeatures: { 25 includedFeatures: {
35 id: 'subscription.includedFeatures', 26 id: 'subscription.teaser.includedFeatures',
36 defaultMessage: '!!!The Franz Premium Supporter Account includes', 27 defaultMessage: '!!!Paid Franz Plans include:',
37 },
38 onpremise: {
39 id: 'subscription.features.onpremise.mattermost',
40 defaultMessage: '!!!Add on-premise/hosted services like Mattermost',
41 },
42 noInterruptions: {
43 id: 'subscription.features.noInterruptions',
44 defaultMessage: '!!!No app delays & nagging to upgrade license',
45 },
46 proxy: {
47 id: 'subscription.features.proxy',
48 defaultMessage: '!!!Proxy support for services',
49 },
50 spellchecker: {
51 id: 'subscription.features.spellchecker',
52 defaultMessage: '!!!Support for Spellchecker',
53 }, 28 },
54 workspaces: { 29});
55 id: 'subscription.features.workspaces', 30
56 defaultMessage: '!!!Organize your services in workspaces', 31const styles = () => ({
57 }, 32 activateTrialButton: {
58 ads: { 33 margin: [40, 0, 50],
59 id: 'subscription.features.ads',
60 defaultMessage: '!!!No ads, ever!',
61 },
62 comingSoon: {
63 id: 'subscription.features.comingSoon',
64 defaultMessage: '!!!coming soon',
65 },
66 euTaxInfo: {
67 id: 'subscription.euTaxInfo',
68 defaultMessage: '!!!EU residents: local sales tax may apply',
69 }, 34 },
70}); 35});
71 36
72export default @observer class SubscriptionForm extends Component { 37export default @observer @injectSheet(styles) class SubscriptionForm extends Component {
73 static propTypes = { 38 static propTypes = {
74 plan: MobxPropTypes.objectOrObservableObject.isRequired, 39 selectPlan: PropTypes.func.isRequired,
75 isLoading: PropTypes.bool.isRequired, 40 isActivatingTrial: PropTypes.bool.isRequired,
76 handlePayment: PropTypes.func.isRequired, 41 classes: PropTypes.object.isRequired,
77 retryPlanRequest: PropTypes.func.isRequired,
78 isCreatingHostedPage: PropTypes.bool.isRequired,
79 error: PropTypes.bool.isRequired,
80 showSkipOption: PropTypes.bool,
81 skipAction: PropTypes.func,
82 skipButtonLabel: PropTypes.string,
83 hideInfo: PropTypes.bool.isRequired,
84 };
85
86 static defaultProps = {
87 showSkipOption: false,
88 skipAction: () => null,
89 skipButtonLabel: '',
90 }; 42 };
91 43
92 static contextTypes = { 44 static contextTypes = {
93 intl: intlShape, 45 intl: intlShape,
94 }; 46 };
95 47
96 componentWillMount() {
97 this.form = this.prepareForm();
98 }
99
100 prepareForm() {
101 const { intl } = this.context;
102
103 const form = {
104 fields: {
105 paymentTier: {
106 value: 'year',
107 validators: [required],
108 options: [{
109 value: 'month',
110 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month')
111 ? `${this.props.plan.month.price} / ${intl.formatMessage(messages.typeMonthly)}`
112 : 'monthly'}`,
113 }, {
114 value: 'year',
115 label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year')
116 ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}`
117 : 'yearly'}`,
118 }],
119 },
120 },
121 };
122
123 if (this.props.showSkipOption) {
124 form.fields.paymentTier.options.unshift({
125 value: 'skip',
126 label: `€ 0 / ${intl.formatMessage(messages.typeFree)}`,
127 });
128 }
129
130 return new Form(form, this.context.intl);
131 }
132
133 render() { 48 render() {
134 const { 49 const {
135 isLoading, 50 isActivatingTrial,
136 isCreatingHostedPage, 51 selectPlan,
137 handlePayment, 52 classes,
138 retryPlanRequest,
139 error,
140 showSkipOption,
141 skipAction,
142 skipButtonLabel,
143 hideInfo,
144 } = this.props; 53 } = this.props;
145 const { intl } = this.context; 54 const { intl } = this.context;
146 55
147 if (error) { 56 return (
148 return ( 57 <>
58 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
59 <p>{intl.formatMessage(messages.teaserText)}</p>
149 <Button 60 <Button
150 label="Reload" 61 label={intl.formatMessage(messages.submitButtonLabel)}
151 onClick={retryPlanRequest} 62 className={classes.activateTrialButton}
152 isLoaded={!isLoading} 63 busy={isActivatingTrial}
64 onClick={selectPlan}
65 stretch
153 /> 66 />
154 ); 67 <div className="subscription__premium-info">
155 } 68 <H3>
156 69 {intl.formatMessage(messages.includedFeatures)}
157 return ( 70 </H3>
158 <Loader loaded={!isLoading}> 71 <div className="subscription">
159 <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> 72 <FeatureList />
160 {!hideInfo && (
161 <div className="subscription__premium-info">
162 <p>
163 <strong>{intl.formatMessage(messages.includedFeatures)}</strong>
164 </p>
165 <div className="subscription">
166 <ul className="subscription__premium-features">
167 <li>{intl.formatMessage(messages.onpremise)}</li>
168 <li>
169 {intl.formatMessage(messages.noInterruptions)}
170 </li>
171 <li>
172 {intl.formatMessage(messages.spellchecker)}
173 </li>
174 <li>
175 {intl.formatMessage(messages.proxy)}
176 </li>
177 <li>
178 {intl.formatMessage(messages.workspaces)}
179 </li>
180 <li>
181 {intl.formatMessage(messages.ads)}
182 </li>
183 </ul>
184 </div>
185 </div> 73 </div>
186 )} 74 </div>
187 <Fragment> 75 </>
188 {error.code === 'no-payment-session' && (
189 <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p>
190 )}
191 </Fragment>
192 {showSkipOption && this.form.$('paymentTier').value === 'skip' ? (
193 <Button
194 label={skipButtonLabel}
195 className="auth__button"
196 onClick={skipAction}
197 />
198 ) : (
199 <Button
200 label={intl.formatMessage(messages.submitButtonLabel)}
201 className="auth__button"
202 loaded={!isCreatingHostedPage}
203 onClick={() => handlePayment(this.form.$('paymentTier').value)}
204 />
205 )}
206 {this.form.$('paymentTier').value !== 'skip' && (
207 <p className="legal">
208 {intl.formatMessage(messages.euTaxInfo)}
209 </p>
210 )}
211 </Loader>
212 ); 76 );
213 } 77 }
214} 78}
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js
index 0f6f0260f..12ef8a6e9 100644
--- a/src/components/subscription/SubscriptionPopup.js
+++ b/src/components/subscription/SubscriptionPopup.js
@@ -43,7 +43,7 @@ export default @observer class SubscriptionPopup extends Component {
43 43
44 setTimeout(() => { 44 setTimeout(() => {
45 this.props.closeWindow(); 45 this.props.closeWindow();
46 }, ms('4s')); 46 }, ms('1s'));
47 } 47 }
48 48
49 render() { 49 render() {
@@ -61,8 +61,6 @@ export default @observer class SubscriptionPopup extends Component {
61 autosize 61 autosize
62 src={encodeURI(url)} 62 src={encodeURI(url)}
63 onDidNavigate={completeCheck} 63 onDidNavigate={completeCheck}
64 // onNewWindow={(event, url, frameName, options) =>
65 // openWindow({ event, url, frameName, options })}
66 /> 64 />
67 </div> 65 </div>
68 <div className="subscription-popup__toolbar franz-form"> 66 <div className="subscription-popup__toolbar franz-form">
diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js
new file mode 100644
index 000000000..9ed548f16
--- /dev/null
+++ b/src/components/subscription/TrialForm.js
@@ -0,0 +1,115 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import { H3, H2 } from '@meetfranz/ui';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
11import { FeatureItem } from '../ui/FeatureItem';
12
13const messages = defineMessages({
14 submitButtonLabel: {
15 id: 'subscription.cta.activateTrial',
16 defaultMessage: '!!!Yes, start the free Franz Professional trial',
17 },
18 allOptionsButton: {
19 id: 'subscription.cta.allOptions',
20 defaultMessage: '!!!See all options',
21 },
22 teaserHeadline: {
23 id: 'settings.account.headlineTrialUpgrade',
24 defaultMessage: '!!!Get the free 14 day Franz Professional Trial',
25 },
26 includedFeatures: {
27 id: 'subscription.includedProFeatures',
28 defaultMessage: '!!!The Franz Professional Plan includes:',
29 },
30 noStringsAttachedHeadline: {
31 id: 'pricing.trial.terms.headline',
32 defaultMessage: '!!!No strings attached',
33 },
34 noCreditCard: {
35 id: 'pricing.trial.terms.noCreditCard',
36 defaultMessage: '!!!No credit card required',
37 },
38 automaticTrialEnd: {
39 id: 'pricing.trial.terms.automaticTrialEnd',
40 defaultMessage: '!!!Your free trial ends automatically after 14 days',
41 },
42});
43
44const styles = theme => ({
45 activateTrialButton: {
46 margin: [40, 0, 10],
47 },
48 allOptionsButton: {
49 margin: [0, 0, 40],
50 background: 'none',
51 border: 'none',
52 color: theme.colorText,
53 },
54 keyTerms: {
55 marginTop: 20,
56 },
57});
58
59export default @observer @injectSheet(styles) class TrialForm extends Component {
60 static propTypes = {
61 activateTrial: PropTypes.func.isRequired,
62 isActivatingTrial: PropTypes.bool.isRequired,
63 showAllOptions: PropTypes.func.isRequired,
64 classes: PropTypes.object.isRequired,
65 };
66
67 static contextTypes = {
68 intl: intlShape,
69 };
70
71 render() {
72 const {
73 isActivatingTrial,
74 activateTrial,
75 showAllOptions,
76 classes,
77 } = this.props;
78 const { intl } = this.context;
79
80 return (
81 <>
82 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
83 <H3 className={classes.keyTerms}>
84 {intl.formatMessage(messages.noStringsAttachedHeadline)}
85 </H3>
86 <ul>
87 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
88 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
89 </ul>
90
91 <Button
92 label={intl.formatMessage(messages.submitButtonLabel)}
93 className={classes.activateTrialButton}
94 busy={isActivatingTrial}
95 onClick={activateTrial}
96 stretch
97 />
98 <Button
99 label={intl.formatMessage(messages.allOptionsButton)}
100 className={classes.allOptionsButton}
101 onClick={showAllOptions}
102 stretch
103 />
104 <div className="subscription__premium-info">
105 <H3>
106 {intl.formatMessage(messages.includedFeatures)}
107 </H3>
108 <div className="subscription">
109 <FeatureList />
110 </div>
111 </div>
112 </>
113 );
114 }
115}
diff --git a/src/components/ui/ActivateTrialButton/index.js b/src/components/ui/ActivateTrialButton/index.js
new file mode 100644
index 000000000..c3e5f4a6f
--- /dev/null
+++ b/src/components/ui/ActivateTrialButton/index.js
@@ -0,0 +1,115 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import classnames from 'classnames';
6
7import { Button } from '@meetfranz/forms';
8import { gaEvent } from '../../../lib/analytics';
9
10import UserStore from '../../../stores/UserStore';
11
12const messages = defineMessages({
13 action: {
14 id: 'feature.delayApp.upgrade.action',
15 defaultMessage: '!!!Get a Franz Supporter License',
16 },
17 actionTrial: {
18 id: 'feature.delayApp.trial.action',
19 defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional',
20 },
21 shortAction: {
22 id: 'feature.delayApp.upgrade.actionShort',
23 defaultMessage: '!!!Upgrade account',
24 },
25 shortActionTrial: {
26 id: 'feature.delayApp.trial.actionShort',
27 defaultMessage: '!!!Activate the free Franz Professional trial',
28 },
29});
30
31@inject('stores', 'actions') @observer
32class ActivateTrialButton extends Component {
33 static propTypes = {
34 // eslint-disable-next-line
35 classes: PropTypes.object.isRequired,
36 className: PropTypes.string,
37 short: PropTypes.bool,
38 gaEventInfo: PropTypes.shape({
39 category: PropTypes.string.isRequired,
40 event: PropTypes.string.isRequired,
41 label: PropTypes.string,
42 }),
43 };
44
45 static defaultProps = {
46 className: '',
47 short: false,
48 gaEventInfo: null,
49 }
50
51 static contextTypes = {
52 intl: intlShape,
53 };
54
55 handleCTAClick() {
56 const { actions, stores, gaEventInfo } = this.props;
57 const { hadSubscription } = stores.user.data;
58 const { defaultTrialPlan } = stores.features.features;
59
60 let label = '';
61 if (!hadSubscription) {
62 actions.user.activateTrial({ planId: defaultTrialPlan });
63
64 label = 'Start Trial';
65 } else {
66 actions.ui.openSettings({ path: 'user' });
67
68 label = 'Upgrade Account';
69 }
70
71 if (gaEventInfo) {
72 const { category, event } = gaEventInfo;
73 gaEvent(category, event, label);
74 }
75 }
76
77 render() {
78 const { stores, className, short } = this.props;
79 const { intl } = this.context;
80
81 const { hadSubscription } = stores.user.data;
82
83 let label;
84 if (hadSubscription) {
85 label = short ? messages.shortAction : messages.action;
86 } else {
87 label = short ? messages.shortActionTrial : messages.actionTrial;
88 }
89
90 return (
91 <Button
92 label={intl.formatMessage(label)}
93 className={classnames({
94 [className]: className,
95 })}
96 buttonType="inverted"
97 onClick={this.handleCTAClick.bind(this)}
98 busy={stores.user.activateTrialRequest.isExecuting}
99 />
100 );
101 }
102}
103
104export default ActivateTrialButton;
105
106ActivateTrialButton.wrappedComponent.propTypes = {
107 stores: PropTypes.shape({
108 user: PropTypes.instanceOf(UserStore).isRequired,
109 }).isRequired,
110 actions: PropTypes.shape({
111 ui: PropTypes.shape({
112 openSettings: PropTypes.func.isRequired,
113 }).isRequired,
114 }).isRequired,
115};
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
new file mode 100644
index 000000000..7c482c4d4
--- /dev/null
+++ b/src/components/ui/FeatureItem.js
@@ -0,0 +1,37 @@
1import React from 'react';
2import injectSheet from 'react-jss';
3import { Icon } from '@meetfranz/ui';
4import classnames from 'classnames';
5import { mdiCheckCircle } from '@mdi/js';
6
7const styles = theme => ({
8 featureItem: {
9 borderBottom: [1, 'solid', theme.defaultContentBorder],
10 padding: [8, 0],
11 display: 'flex',
12 alignItems: 'center',
13 },
14 featureIcon: {
15 fill: theme.brandSuccess,
16 marginRight: 10,
17 },
18});
19
20export const FeatureItem = injectSheet(styles)(({
21 classes, className, name, icon,
22}) => (
23 <li className={classnames({
24 [classes.featureItem]: true,
25 [className]: className,
26 })}
27 >
28 {icon ? (
29 <span className={classes.featureIcon}>{icon}</span>
30 ) : (
31 <Icon icon={mdiCheckCircle} className={classes.featureIcon} size={1.5} />
32 )}
33 {name}
34 </li>
35));
36
37export default FeatureItem;
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
new file mode 100644
index 000000000..62944ad75
--- /dev/null
+++ b/src/components/ui/FeatureList.js
@@ -0,0 +1,89 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4
5import { FeatureItem } from './FeatureItem';
6
7const messages = defineMessages({
8 unlimitedServices: {
9 id: 'pricing.features.unlimitedServices',
10 defaultMessage: '!!!Add unlimited services',
11 },
12 spellchecker: {
13 id: 'pricing.features.spellchecker',
14 defaultMessage: '!!!Spellchecker support',
15 },
16 workspaces: {
17 id: 'pricing.features.workspaces',
18 defaultMessage: '!!!Workspaces',
19 },
20 customWebsites: {
21 id: 'pricing.features.customWebsites',
22 defaultMessage: '!!!Add Custom Websites',
23 },
24 onPremise: {
25 id: 'pricing.features.onPremise',
26 defaultMessage: '!!!On-premise & other Hosted Services',
27 },
28 thirdPartyServices: {
29 id: 'pricing.features.thirdPartyServices',
30 defaultMessage: '!!!Install 3rd party services',
31 },
32 serviceProxies: {
33 id: 'pricing.features.serviceProxies',
34 defaultMessage: '!!!Service Proxies',
35 },
36 teamManagement: {
37 id: 'pricing.features.teamManagement',
38 defaultMessage: '!!!Team Management',
39 },
40 appDelays: {
41 id: 'pricing.features.appDelays',
42 defaultMessage: '!!!No Waiting Screens',
43 },
44 adFree: {
45 id: 'pricing.features.adFree',
46 defaultMessage: '!!!Forever ad-free',
47 },
48});
49
50export class FeatureList extends Component {
51 static propTypes = {
52 className: PropTypes.string,
53 featureClassName: PropTypes.string,
54 };
55
56 static defaultProps = {
57 className: '',
58 featureClassName: '',
59 }
60
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 render() {
66 const {
67 className,
68 featureClassName,
69 } = this.props;
70 const { intl } = this.context;
71
72 return (
73 <ul className={className}>
74 <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} className={featureClassName} />
75 <FeatureItem name={intl.formatMessage(messages.spellchecker)} className={featureClassName} />
76 <FeatureItem name={intl.formatMessage(messages.workspaces)} className={featureClassName} />
77 <FeatureItem name={intl.formatMessage(messages.customWebsites)} className={featureClassName} />
78 <FeatureItem name={intl.formatMessage(messages.onPremise)} className={featureClassName} />
79 <FeatureItem name={intl.formatMessage(messages.thirdPartyServices)} className={featureClassName} />
80 <FeatureItem name={intl.formatMessage(messages.serviceProxies)} className={featureClassName} />
81 <FeatureItem name={intl.formatMessage(messages.teamManagement)} className={featureClassName} />
82 <FeatureItem name={intl.formatMessage(messages.appDelays)} className={featureClassName} />
83 <FeatureItem name={intl.formatMessage(messages.adFree)} className={featureClassName} />
84 </ul>
85 );
86 }
87}
88
89export default FeatureList;
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index 0b7154760..63d858c47 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -5,6 +5,7 @@ import classnames from 'classnames';
5import injectCSS from 'react-jss'; 5import injectCSS from 'react-jss';
6import { Icon } from '@meetfranz/ui'; 6import { Icon } from '@meetfranz/ui';
7 7
8import { mdiClose } from '@mdi/js';
8import styles from './styles'; 9import styles from './styles';
9 10
10// ReactModal.setAppElement('#root'); 11// ReactModal.setAppElement('#root');
@@ -59,7 +60,7 @@ export default @injectCSS(styles) class Modal extends Component {
59 className={classes.close} 60 className={classes.close}
60 onClick={close} 61 onClick={close}
61 > 62 >
62 <Icon icon="mdiClose" size={1.5} /> 63 <Icon icon={mdiClose} size={1.5} />
63 </button> 64 </button>
64 )} 65 )}
65 <div className={classes.content}> 66 <div className={classes.content}>
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index 3c1e0fac3..8d2746e22 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -10,6 +10,7 @@ import UserStore from '../../../stores/UserStore';
10 10
11import styles from './styles'; 11import styles from './styles';
12import { gaEvent } from '../../../lib/analytics'; 12import { gaEvent } from '../../../lib/analytics';
13import { FeatureStore } from '../../../features/utils/FeatureStore';
13 14
14const messages = defineMessages({ 15const messages = defineMessages({
15 action: { 16 action: {
@@ -22,7 +23,10 @@ const messages = defineMessages({
22class PremiumFeatureContainer extends Component { 23class PremiumFeatureContainer extends Component {
23 static propTypes = { 24 static propTypes = {
24 classes: PropTypes.object.isRequired, 25 classes: PropTypes.object.isRequired,
25 condition: PropTypes.bool, 26 condition: PropTypes.oneOfType([
27 PropTypes.bool,
28 PropTypes.func,
29 ]),
26 gaEventInfo: PropTypes.shape({ 30 gaEventInfo: PropTypes.shape({
27 category: PropTypes.string.isRequired, 31 category: PropTypes.string.isRequired,
28 event: PropTypes.string.isRequired, 32 event: PropTypes.string.isRequired,
@@ -31,7 +35,7 @@ class PremiumFeatureContainer extends Component {
31 }; 35 };
32 36
33 static defaultProps = { 37 static defaultProps = {
34 condition: true, 38 condition: null,
35 gaEventInfo: null, 39 gaEventInfo: null,
36 }; 40 };
37 41
@@ -51,7 +55,18 @@ class PremiumFeatureContainer extends Component {
51 55
52 const { intl } = this.context; 56 const { intl } = this.context;
53 57
54 return !stores.user.data.isPremium && !!condition ? ( 58 let showWrapper = !!condition;
59
60 if (condition === null) {
61 showWrapper = !stores.user.data.isPremium;
62 } else if (typeof condition === 'function') {
63 showWrapper = condition({
64 isPremium: stores.user.data.isPremium,
65 features: stores.features.features,
66 });
67 }
68
69 return showWrapper ? (
55 <div className={classes.container}> 70 <div className={classes.container}>
56 <div className={classes.titleContainer}> 71 <div className={classes.titleContainer}>
57 <p className={classes.title}>Premium Feature</p> 72 <p className={classes.title}>Premium Feature</p>
@@ -81,6 +96,7 @@ PremiumFeatureContainer.wrappedComponent.propTypes = {
81 children: oneOrManyChildElements.isRequired, 96 children: oneOrManyChildElements.isRequired,
82 stores: PropTypes.shape({ 97 stores: PropTypes.shape({
83 user: PropTypes.instanceOf(UserStore).isRequired, 98 user: PropTypes.instanceOf(UserStore).isRequired,
99 features: PropTypes.instanceOf(FeatureStore).isRequired,
84 }).isRequired, 100 }).isRequired,
85 actions: PropTypes.shape({ 101 actions: PropTypes.shape({
86 ui: PropTypes.shape({ 102 ui: PropTypes.shape({
diff --git a/src/config.js b/src/config.js
index 471f8d5a6..fba739ddd 100644
--- a/src/config.js
+++ b/src/config.js
@@ -19,7 +19,8 @@ export const DEV_WS_API = 'wss://dev.franzinfra.com';
19export const LIVE_WS_API = 'wss://api.franzinfra.com'; 19export const LIVE_WS_API = 'wss://api.franzinfra.com';
20 20
21export const LOCAL_API_WEBSITE = 'http://localhost:3333'; 21export const LOCAL_API_WEBSITE = 'http://localhost:3333';
22export const DEV_API_WEBSITE = 'https://meetfranz.com'; 22// export const DEV_API_WEBSITE = 'https://meetfranz.com';t
23export const DEV_API_WEBSITE = 'http://hash-3ac3ccd2472269cf585c58a4f6973d86f3c9e7bd.franzstaging.com/'; // TODO: revert me
23export const LIVE_API_WEBSITE = 'https://meetfranz.com'; 24export const LIVE_API_WEBSITE = 'https://meetfranz.com';
24 25
25export const STATS_API = 'https://stats.franzinfra.com'; 26export const STATS_API = 'https://stats.franzinfra.com';
@@ -49,16 +50,16 @@ export const DEFAULT_APP_SETTINGS = {
49}; 50};
50 51
51export const DEFAULT_FEATURES_CONFIG = { 52export const DEFAULT_FEATURES_CONFIG = {
52 isSpellcheckerPremiumFeature: false, 53 isSpellcheckerIncludedInCurrentPlan: true,
53 needToWaitToProceed: false, 54 needToWaitToProceed: false,
54 needToWaitToProceedConfig: { 55 needToWaitToProceedConfig: {
55 delayOffset: ms('1h'), 56 delayOffset: ms('1h'),
56 wait: ms('10s'), 57 wait: ms('10s'),
57 }, 58 },
58 isServiceProxyEnabled: false, 59 isServiceProxyEnabled: false,
59 isServiceProxyPremiumFeature: true, 60 isServiceProxyIncludedInCurrentPlan: false,
60 isAnnouncementsEnabled: true, 61 isAnnouncementsEnabled: true,
61 isWorkspacePremiumFeature: true, 62 isWorkspaceIncludedInCurrentPlan: true,
62 isWorkspaceEnabled: false, 63 isWorkspaceEnabled: false,
63}; 64};
64 65
@@ -71,6 +72,7 @@ export const DEFAULT_WINDOW_OPTIONS = {
71 72
72export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs'; 73export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs';
73export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; 74export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate';
75export const FRANZ_DEV_DOCS = 'http://bit.ly/franz-dev-hub';
74 76
75export const FILE_SYSTEM_SETTINGS_TYPES = [ 77export const FILE_SYSTEM_SETTINGS_TYPES = [
76 'app', 78 'app',
@@ -87,3 +89,25 @@ export const ALLOWED_PROTOCOLS = [
87 'http:', 89 'http:',
88 'ftp:', 90 'ftp:',
89]; 91];
92
93export const PLANS = {
94 PERSONAL: 'PERSONAL',
95 PRO: 'PRO',
96 LEGACY: 'LEGACY',
97 FREE: 'FREE',
98};
99
100export const PLANS_MAPPING = {
101 'franz-personal-monthly': PLANS.PERSONAL,
102 'franz-personal-yearly': PLANS.PERSONAL,
103 'franz-pro-monthly': PLANS.PRO,
104 'franz-pro-yearly': PLANS.PRO,
105 'franz-supporter-license': PLANS.LEGACY,
106 'franz-supporter-license-x1': PLANS.LEGACY,
107 'franz-supporter-license-x2': PLANS.LEGACY,
108 'franz-supporter-license-year': PLANS.LEGACY,
109 'franz-supporter-license-year-x1': PLANS.LEGACY,
110 'franz-supporter-license-year-x2': PLANS.LEGACY,
111 'franz-supporter-license-year-2019': PLANS.LEGACY,
112 free: PLANS.FREE,
113};
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js
index 8d179a170..af1651931 100644
--- a/src/containers/auth/PricingScreen.js
+++ b/src/containers/auth/PricingScreen.js
@@ -5,7 +5,6 @@ import { RouterStore } from 'mobx-react-router';
5 5
6import Pricing from '../../components/auth/Pricing'; 6import Pricing from '../../components/auth/Pricing';
7import UserStore from '../../stores/UserStore'; 7import UserStore from '../../stores/UserStore';
8import PaymentStore from '../../stores/PaymentStore';
9 8
10import { globalError as globalErrorPropType } from '../../prop-types'; 9import { globalError as globalErrorPropType } from '../../prop-types';
11 10
@@ -14,20 +13,40 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
14 error: globalErrorPropType.isRequired, 13 error: globalErrorPropType.isRequired,
15 }; 14 };
16 15
16 async submit() {
17 const {
18 actions,
19 stores,
20 } = this.props;
21
22 const { activateTrialRequest } = stores.user;
23 const { defaultTrialPlan } = stores.features.features;
24
25 actions.user.activateTrial({ planId: defaultTrialPlan });
26 await activateTrialRequest._promise;
27
28 if (!activateTrialRequest.isError) {
29 stores.router.push('/');
30 stores.user.hasCompletedSignup = true;
31 }
32 }
33
17 render() { 34 render() {
18 const { actions, stores, error } = this.props; 35 const {
36 error,
37 stores,
38 } = this.props;
19 39
20 const nextStepRoute = stores.user.legacyServices.length ? stores.user.importRoute : stores.user.inviteRoute; 40 const { getUserInfoRequest, activateTrialRequest } = stores.user;
41 const { featuresRequest } = stores.features;
21 42
22 return ( 43 return (
23 <Pricing 44 <Pricing
24 donor={stores.user.data.donor || {}} 45 onSubmit={this.submit.bind(this)}
25 onSubmit={actions.user.signup} 46 isLoadingRequiredData={(getUserInfoRequest.isExecuting || !getUserInfoRequest.wasExecuted) || (featuresRequest.isExecuting || !featuresRequest.wasExecuted)}
26 onCloseSubscriptionWindow={() => this.props.stores.router.push(nextStepRoute)} 47 isActivatingTrial={activateTrialRequest.isExecuting}
27 isLoading={stores.payment.plansRequest.isExecuting} 48 trialActivationError={activateTrialRequest.isError}
28 isLoadingUser={stores.user.getUserInfoRequest.isExecuting}
29 error={error} 49 error={error}
30 skipAction={() => this.props.stores.router.push(nextStepRoute)}
31 /> 50 />
32 ); 51 );
33 } 52 }
@@ -36,12 +55,11 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend
36PricingScreen.wrappedComponent.propTypes = { 55PricingScreen.wrappedComponent.propTypes = {
37 actions: PropTypes.shape({ 56 actions: PropTypes.shape({
38 user: PropTypes.shape({ 57 user: PropTypes.shape({
39 signup: PropTypes.func.isRequired, 58 activateTrial: PropTypes.func.isRequired,
40 }).isRequired, 59 }).isRequired,
41 }).isRequired, 60 }).isRequired,
42 stores: PropTypes.shape({ 61 stores: PropTypes.shape({
43 user: PropTypes.instanceOf(UserStore).isRequired, 62 user: PropTypes.instanceOf(UserStore).isRequired,
44 payment: PropTypes.instanceOf(PaymentStore).isRequired,
45 router: PropTypes.instanceOf(RouterStore).isRequired, 63 router: PropTypes.instanceOf(RouterStore).isRequired,
46 }).isRequired, 64 }).isRequired,
47}; 65};
diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js
index cf3da71e8..a14a98554 100644
--- a/src/containers/layout/AppLayoutContainer.js
+++ b/src/containers/layout/AppLayoutContainer.js
@@ -10,6 +10,7 @@ import FeaturesStore from '../../stores/FeaturesStore';
10import UIStore from '../../stores/UIStore'; 10import UIStore from '../../stores/UIStore';
11import NewsStore from '../../stores/NewsStore'; 11import NewsStore from '../../stores/NewsStore';
12import SettingsStore from '../../stores/SettingsStore'; 12import SettingsStore from '../../stores/SettingsStore';
13import UserStore from '../../stores/UserStore';
13import RequestStore from '../../stores/RequestStore'; 14import RequestStore from '../../stores/RequestStore';
14import GlobalErrorStore from '../../stores/GlobalErrorStore'; 15import GlobalErrorStore from '../../stores/GlobalErrorStore';
15 16
@@ -39,6 +40,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
39 settings, 40 settings,
40 globalError, 41 globalError,
41 requests, 42 requests,
43 user,
42 } = this.props.stores; 44 } = this.props.stores;
43 45
44 const { 46 const {
@@ -125,6 +127,8 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
125 reload={reload} 127 reload={reload}
126 openSettings={openSettings} 128 openSettings={openSettings}
127 update={updateService} 129 update={updateService}
130 userHasCompletedSignup={user.hasCompletedSignup}
131 hasActivatedTrial={user.hasActivatedTrial}
128 /> 132 />
129 ); 133 );
130 134
@@ -149,6 +153,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e
149 retryRequiredRequests={retryRequiredRequests} 153 retryRequiredRequests={retryRequiredRequests}
150 areRequiredRequestsLoading={requests.areRequiredRequestsLoading} 154 areRequiredRequestsLoading={requests.areRequiredRequestsLoading}
151 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible} 155 isDelayAppScreenVisible={delayAppState.isDelayAppScreenVisible}
156 hasActivatedTrial={user.hasActivatedTrial}
152 > 157 >
153 {React.Children.count(children) > 0 ? children : null} 158 {React.Children.count(children) > 0 ? children : null}
154 </AppLayout> 159 </AppLayout>
@@ -166,6 +171,7 @@ AppLayoutContainer.wrappedComponent.propTypes = {
166 ui: PropTypes.instanceOf(UIStore).isRequired, 171 ui: PropTypes.instanceOf(UIStore).isRequired,
167 news: PropTypes.instanceOf(NewsStore).isRequired, 172 news: PropTypes.instanceOf(NewsStore).isRequired,
168 settings: PropTypes.instanceOf(SettingsStore).isRequired, 173 settings: PropTypes.instanceOf(SettingsStore).isRequired,
174 user: PropTypes.instanceOf(UserStore).isRequired,
169 requests: PropTypes.instanceOf(RequestStore).isRequired, 175 requests: PropTypes.instanceOf(RequestStore).isRequired,
170 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, 176 globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired,
171 }).isRequired, 177 }).isRequired,
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js
index 66076504f..2b5eba91c 100644
--- a/src/containers/settings/AccountScreen.js
+++ b/src/containers/settings/AccountScreen.js
@@ -26,7 +26,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
26 handleWebsiteLink(route) { 26 handleWebsiteLink(route) {
27 const { actions, stores } = this.props; 27 const { actions, stores } = this.props;
28 28
29 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`; 29 const url = stores.user.getAuthURL(`${WEBSITE}${route}?utm_source=app&utm_medium=account_dashboard`);
30 30
31 actions.app.openExternalUrl({ url }); 31 actions.app.openExternalUrl({ url });
32 } 32 }
@@ -42,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
42 <ErrorBoundary> 42 <ErrorBoundary>
43 <AccountDashboard 43 <AccountDashboard
44 user={user.data} 44 user={user.data}
45 isProUser={user.isPro}
45 isLoading={isLoadingUserInfo} 46 isLoading={isLoadingUserInfo}
46 isLoadingPlans={isLoadingPlans} 47 isLoadingPlans={isLoadingPlans}
47 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError} 48 userInfoRequestFailed={user.getUserInfoRequest.wasExecuted && user.getUserInfoRequest.isError}
@@ -51,6 +52,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend
51 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting} 52 isLoadingDeleteAccount={user.deleteAccountRequest.isExecuting}
52 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError} 53 isDeleteAccountSuccessful={user.deleteAccountRequest.wasExecuted && !user.deleteAccountRequest.isError}
53 openEditAccount={() => this.handleWebsiteLink('/user/profile')} 54 openEditAccount={() => this.handleWebsiteLink('/user/profile')}
55 upgradeToPro={() => this.handleWebsiteLink('/inapp/user/licenses')}
54 openBilling={() => this.handleWebsiteLink('/user/billing')} 56 openBilling={() => this.handleWebsiteLink('/user/billing')}
55 openInvoices={() => this.handleWebsiteLink('/user/invoices')} 57 openInvoices={() => this.handleWebsiteLink('/user/invoices')}
56 /> 58 />
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js
index 870ca4ecd..e4ff03bb3 100644
--- a/src/containers/settings/EditServiceScreen.js
+++ b/src/containers/settings/EditServiceScreen.js
@@ -330,8 +330,8 @@ export default @inject('stores', 'actions') @observer class EditServiceScreen ex
330 onSubmit={d => this.onSubmit(d)} 330 onSubmit={d => this.onSubmit(d)}
331 onDelete={() => this.deleteService()} 331 onDelete={() => this.deleteService()}
332 isProxyFeatureEnabled={proxyFeature.isEnabled} 332 isProxyFeatureEnabled={proxyFeature.isEnabled}
333 isProxyPremiumFeature={proxyFeature.isPremium} 333 isServiceProxyIncludedInCurrentPlan={proxyFeature.isIncludedInCurrentPlan}
334 isSpellcheckerPremiumFeature={spellcheckerFeature.isPremium} 334 isSpellcheckerIncludedInCurrentPlan={spellcheckerFeature.isIncludedInCurrentPlan}
335 /> 335 />
336 </ErrorBoundary> 336 </ErrorBoundary>
337 ); 337 );
diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js
index 97c1fa3b1..67d52f102 100644
--- a/src/containers/settings/EditSettingsScreen.js
+++ b/src/containers/settings/EditSettingsScreen.js
@@ -159,8 +159,8 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
159 }, 159 },
160 enableSpellchecking: { 160 enableSpellchecking: {
161 label: intl.formatMessage(messages.enableSpellchecking), 161 label: intl.formatMessage(messages.enableSpellchecking),
162 value: !this.props.stores.user.data.isPremium && spellcheckerConfig.isPremium ? false : settings.all.app.enableSpellchecking, 162 value: !this.props.stores.user.data.isPremium && !spellcheckerConfig.isIncludedInCurrentPlan ? false : settings.all.app.enableSpellchecking,
163 default: !this.props.stores.user.data.isPremium && spellcheckerConfig.isPremium ? false : DEFAULT_APP_SETTINGS.enableSpellchecking, 163 default: !this.props.stores.user.data.isPremium && !spellcheckerConfig.isIncludedInCurrentPlan ? false : DEFAULT_APP_SETTINGS.enableSpellchecking,
164 }, 164 },
165 spellcheckerLanguage: { 165 spellcheckerLanguage: {
166 label: intl.formatMessage(globalMessages.spellcheckerLanguage), 166 label: intl.formatMessage(globalMessages.spellcheckerLanguage),
@@ -223,7 +223,7 @@ export default @inject('stores', 'actions') @observer class EditSettingsScreen e
223 cacheSize={cacheSize} 223 cacheSize={cacheSize}
224 isClearingAllCache={isClearingAllCache} 224 isClearingAllCache={isClearingAllCache}
225 onClearAllCache={clearAllCache} 225 onClearAllCache={clearAllCache}
226 isSpellcheckerPremiumFeature={spellcheckerConfig.isPremium} 226 isSpellcheckerIncludedInCurrentPlan={spellcheckerConfig.isIncludedInCurrentPlan}
227 /> 227 />
228 </ErrorBoundary> 228 </ErrorBoundary>
229 ); 229 );
diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js
index eda5ae54c..132820b6f 100644
--- a/src/containers/settings/RecipesScreen.js
+++ b/src/containers/settings/RecipesScreen.js
@@ -1,7 +1,9 @@
1import { remote, shell } from 'electron';
1import React, { Component } from 'react'; 2import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 3import PropTypes from 'prop-types';
3import { autorun } from 'mobx'; 4import { autorun } from 'mobx';
4import { inject, observer } from 'mobx-react'; 5import { inject, observer } from 'mobx-react';
6import path from 'path';
5 7
6import RecipePreviewsStore from '../../stores/RecipePreviewsStore'; 8import RecipePreviewsStore from '../../stores/RecipePreviewsStore';
7import RecipeStore from '../../stores/RecipesStore'; 9import RecipeStore from '../../stores/RecipesStore';
@@ -10,6 +12,11 @@ import UserStore from '../../stores/UserStore';
10 12
11import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; 13import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard';
12import ErrorBoundary from '../../components/util/ErrorBoundary'; 14import ErrorBoundary from '../../components/util/ErrorBoundary';
15import { FRANZ_DEV_DOCS } from '../../config';
16import { gaEvent } from '../../lib/analytics';
17import { communityRecipesStore } from '../../features/communityRecipes';
18
19const { app } = remote;
13 20
14export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { 21export default @inject('stores', 'actions') @observer class RecipesScreen extends Component {
15 static propTypes = { 22 static propTypes = {
@@ -67,9 +74,16 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
67 74
68 render() { 75 render() {
69 const { 76 const {
70 recipePreviews, recipes, services, user, 77 recipePreviews,
78 recipes,
79 services,
80 user,
71 } = this.props.stores; 81 } = this.props.stores;
72 const { showAddServiceInterface } = this.props.actions.service; 82
83 const {
84 app: appActions,
85 service: serviceActions,
86 } = this.props.actions;
73 87
74 const { filter } = this.props.params; 88 const { filter } = this.props.params;
75 let recipeFilter; 89 let recipeFilter;
@@ -77,7 +91,7 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
77 if (filter === 'all') { 91 if (filter === 'all') {
78 recipeFilter = recipePreviews.all; 92 recipeFilter = recipePreviews.all;
79 } else if (filter === 'dev') { 93 } else if (filter === 'dev') {
80 recipeFilter = recipePreviews.dev; 94 recipeFilter = communityRecipesStore.communityRecipes;
81 } else { 95 } else {
82 recipeFilter = recipePreviews.featured; 96 recipeFilter = recipePreviews.featured;
83 } 97 }
@@ -89,6 +103,8 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
89 || recipes.installRecipeRequest.isExecuting 103 || recipes.installRecipeRequest.isExecuting
90 || recipePreviews.searchRecipePreviewsRequest.isExecuting; 104 || recipePreviews.searchRecipePreviewsRequest.isExecuting;
91 105
106 const recipeDirectory = path.join(app.getPath('userData'), 'recipes', 'dev');
107
92 return ( 108 return (
93 <ErrorBoundary> 109 <ErrorBoundary>
94 <RecipesDashboard 110 <RecipesDashboard
@@ -97,12 +113,23 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend
97 addedServiceCount={services.all.length} 113 addedServiceCount={services.all.length}
98 isPremium={user.data.isPremium} 114 isPremium={user.data.isPremium}
99 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted} 115 hasLoadedRecipes={recipePreviews.featuredRecipePreviewsRequest.wasExecuted}
100 showAddServiceInterface={showAddServiceInterface} 116 showAddServiceInterface={serviceActions.showAddServiceInterface}
101 searchRecipes={e => this.searchRecipes(e)} 117 searchRecipes={e => this.searchRecipes(e)}
102 resetSearch={() => this.resetSearch()} 118 resetSearch={() => this.resetSearch()}
103 searchNeedle={this.state.needle} 119 searchNeedle={this.state.needle}
104 serviceStatus={services.actionStatus} 120 serviceStatus={services.actionStatus}
105 devRecipesCount={recipePreviews.dev.length} 121 recipeFilter={filter}
122 recipeDirectory={recipeDirectory}
123 openRecipeDirectory={() => {
124 shell.openItem(recipeDirectory);
125 gaEvent('Recipe', 'open-recipe-folder', 'Open Folder');
126 }}
127 openDevDocs={() => {
128 appActions.openExternalUrl({ url: FRANZ_DEV_DOCS });
129 gaEvent('Recipe', 'open-dev-docs', 'Developer Documentation');
130 }}
131 isCommunityRecipesIncludedInCurrentPlan={communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan}
132 isUserPremiumUser={user.isPremium}
106 /> 133 />
107 </ErrorBoundary> 134 </ErrorBoundary>
108 ); 135 );
@@ -117,6 +144,9 @@ RecipesScreen.wrappedComponent.propTypes = {
117 user: PropTypes.instanceOf(UserStore).isRequired, 144 user: PropTypes.instanceOf(UserStore).isRequired,
118 }).isRequired, 145 }).isRequired,
119 actions: PropTypes.shape({ 146 actions: PropTypes.shape({
147 app: PropTypes.shape({
148 openExternalUrl: PropTypes.func.isRequired,
149 }).isRequired,
120 service: PropTypes.shape({ 150 service: PropTypes.shape({
121 showAddServiceInterface: PropTypes.func.isRequired, 151 showAddServiceInterface: PropTypes.func.isRequired,
122 }).isRequired, 152 }).isRequired,
diff --git a/src/containers/settings/TeamScreen.js b/src/containers/settings/TeamScreen.js
index c69d5ad08..b7b1b78cb 100644
--- a/src/containers/settings/TeamScreen.js
+++ b/src/containers/settings/TeamScreen.js
@@ -14,7 +14,6 @@ export default @inject('stores', 'actions') @observer class TeamScreen extends C
14 const { actions, stores } = this.props; 14 const { actions, stores } = this.props;
15 15
16 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`; 16 const url = `${WEBSITE}${route}?authToken=${stores.user.authToken}&utm_source=app&utm_medium=account_dashboard`;
17 console.log(url);
18 17
19 actions.app.openExternalUrl({ url }); 18 actions.app.openExternalUrl({ url });
20 } 19 }
diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js
index aa1166f5e..e9e457084 100644
--- a/src/containers/subscription/SubscriptionFormScreen.js
+++ b/src/containers/subscription/SubscriptionFormScreen.js
@@ -1,4 +1,3 @@
1import { remote } from 'electron';
2import React, { Component } from 'react'; 1import React, { Component } from 'react';
3import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
4import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
@@ -6,96 +5,50 @@ import { inject, observer } from 'mobx-react';
6import PaymentStore from '../../stores/PaymentStore'; 5import PaymentStore from '../../stores/PaymentStore';
7 6
8import SubscriptionForm from '../../components/subscription/SubscriptionForm'; 7import SubscriptionForm from '../../components/subscription/SubscriptionForm';
9 8import TrialForm from '../../components/subscription/TrialForm';
10const { BrowserWindow } = remote;
11 9
12export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { 10export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component {
13 static propTypes = { 11 async openBrowser() {
14 onCloseWindow: PropTypes.func,
15 content: PropTypes.node,
16 showSkipOption: PropTypes.bool,
17 skipAction: PropTypes.func,
18 skipButtonLabel: PropTypes.string,
19 hideInfo: PropTypes.bool,
20 }
21
22 static defaultProps = {
23 onCloseWindow: () => null,
24 content: '',
25 showSkipOption: false,
26 skipAction: () => null,
27 skipButtonLabel: '',
28 hideInfo: false,
29 }
30
31 async handlePayment(plan) {
32 const { 12 const {
33 actions, 13 actions,
34 stores, 14 stores,
35 onCloseWindow,
36 } = this.props; 15 } = this.props;
37 16
38 const interval = plan; 17 const {
39 18 user,
40 const { id } = stores.payment.plan[interval]; 19 features,
41 actions.payment.createHostedPage({ 20 } = stores;
42 planId: id,
43 });
44
45 const hostedPage = await stores.payment.createHostedPageRequest;
46 21
47 if (hostedPage.url) { 22 let hostedPageURL = !user.data.hadSubscription ? features.features.planSelectionURL : features.features.subscribeURL;
48 if (hostedPage.legacyCheckoutFlow) { 23 hostedPageURL = user.getAuthURL(hostedPageURL);
49 const paymentWindow = new BrowserWindow({
50 parent: remote.getCurrentWindow(),
51 modal: true,
52 title: '🔒 Franz Supporter License',
53 width: 600,
54 height: window.innerHeight - 100,
55 maxWidth: 600,
56 minWidth: 600,
57 webPreferences: {
58 nodeIntegration: true,
59 webviewTag: true,
60 },
61 });
62 paymentWindow.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPage.url)}`);
63 24
64 paymentWindow.on('closed', () => { 25 actions.app.openExternalUrl({ url: hostedPageURL });
65 onCloseWindow();
66 });
67 } else {
68 actions.app.openExternalUrl({
69 url: hostedPage.url,
70 });
71 }
72 }
73 } 26 }
74 27
75 render() { 28 render() {
76 const { 29 const {
77 content,
78 actions, 30 actions,
79 stores, 31 stores,
80 showSkipOption,
81 skipAction,
82 skipButtonLabel,
83 hideInfo,
84 } = this.props; 32 } = this.props;
33
34 const { data: user } = stores.user;
35
36 if (user.hadSubscription) {
37 return (
38 <SubscriptionForm
39 plan={stores.payment.plan}
40 selectPlan={() => this.openBrowser()}
41 isActivatingTrial={stores.user.activateTrialRequest.isExecuting || stores.user.getUserInfoRequest.isExecuting}
42 />
43 );
44 }
45
85 return ( 46 return (
86 <SubscriptionForm 47 <TrialForm
87 plan={stores.payment.plan} 48 plan={stores.payment.plan}
88 isLoading={stores.payment.plansRequest.isExecuting} 49 activateTrial={() => actions.user.activateTrial({ planId: stores.features.features.defaultTrialPlan })}
89 retryPlanRequest={() => stores.payment.plansRequest.reload()} 50 showAllOptions={() => this.openBrowser()}
90 isCreatingHostedPage={stores.payment.createHostedPageRequest.isExecuting} 51 isActivatingTrial={stores.user.activateTrialRequest.isExecuting || stores.user.getUserInfoRequest.isExecuting}
91 handlePayment={price => this.handlePayment(price)}
92 content={content}
93 error={stores.payment.plansRequest.isError}
94 showSkipOption={showSkipOption}
95 skipAction={skipAction}
96 skipButtonLabel={skipButtonLabel}
97 hideInfo={hideInfo}
98 openExternalUrl={actions.app.openExternalUrl}
99 /> 52 />
100 ); 53 );
101 } 54 }
diff --git a/src/containers/subscription/SubscriptionPopupScreen.js b/src/containers/subscription/SubscriptionPopupScreen.js
index f76d6c5a6..0de5a87c4 100644
--- a/src/containers/subscription/SubscriptionPopupScreen.js
+++ b/src/containers/subscription/SubscriptionPopupScreen.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react'; 3import { inject, observer } from 'mobx-react';
4 4
5import SubscriptionPopup from '../../components/subscription/SubscriptionPopup'; 5import SubscriptionPopup from '../../components/subscription/SubscriptionPopup';
6import { isDevMode } from '../../environment';
6 7
7 8
8export default @inject('stores', 'actions') @observer class SubscriptionPopupScreen extends Component { 9export default @inject('stores', 'actions') @observer class SubscriptionPopupScreen extends Component {
@@ -13,7 +14,7 @@ export default @inject('stores', 'actions') @observer class SubscriptionPopupScr
13 completeCheck(event) { 14 completeCheck(event) {
14 const { url } = event; 15 const { url } = event;
15 16
16 if ((url.includes('recurly') && url.includes('confirmation')) || (url.includes('meetfranz') && url.includes('success'))) { 17 if ((url.includes('recurly') && url.includes('confirmation')) || ((url.includes('meetfranz') || isDevMode) && url.includes('success'))) {
17 this.setState({ 18 this.setState({
18 complete: true, 19 complete: true,
19 }); 20 });
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js
index a8252acb7..ba9ae2273 100644
--- a/src/features/basicAuth/Component.js
+++ b/src/features/basicAuth/Component.js
@@ -27,7 +27,6 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
27 e.preventDefault(); 27 e.preventDefault();
28 28
29 const values = Form.values(); 29 const values = Form.values();
30 console.log('form submit', values);
31 30
32 sendCredentials(values.user, values.password); 31 sendCredentials(values.user, values.password);
33 resetState(); 32 resetState();
diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.js
new file mode 100644
index 000000000..4d050f90e
--- /dev/null
+++ b/src/features/communityRecipes/index.js
@@ -0,0 +1,28 @@
1import { reaction } from 'mobx';
2import { CommunityRecipesStore } from './store';
3
4const debug = require('debug')('Franz:feature:communityRecipes');
5
6export const DEFAULT_SERVICE_LIMIT = 3;
7
8export const communityRecipesStore = new CommunityRecipesStore();
9
10export default function initCommunityRecipes(stores, actions) {
11 const { features } = stores;
12
13 communityRecipesStore.start(stores, actions);
14
15 // Toggle communityRecipe premium status
16 reaction(
17 () => (
18 features.features.isCommunityRecipesIncludedInCurrentPlan
19 ),
20 (isPremiumFeature) => {
21 debug('Community recipes is premium feature: ', isPremiumFeature);
22 communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan = isPremiumFeature;
23 },
24 {
25 fireImmediately: true,
26 },
27 );
28}
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js
new file mode 100644
index 000000000..4d45c3b33
--- /dev/null
+++ b/src/features/communityRecipes/store.js
@@ -0,0 +1,31 @@
1import { computed, observable } from 'mobx';
2import { FeatureStore } from '../utils/FeatureStore';
3
4const debug = require('debug')('Franz:feature:communityRecipes:store');
5
6export class CommunityRecipesStore extends FeatureStore {
7 @observable isCommunityRecipesIncludedInCurrentPlan = false;
8
9 start(stores, actions) {
10 debug('start');
11 this.stores = stores;
12 this.actions = actions;
13 }
14
15 stop() {
16 debug('stop');
17 super.stop();
18 }
19
20 @computed get communityRecipes() {
21 if (!this.stores) return [];
22
23 return this.stores.recipePreviews.dev.map((r) => {
24 r.isDevRecipe = !!r.author.find(a => a.email === this.stores.user.data.email);
25
26 return r;
27 });
28 }
29}
30
31export default CommunityRecipesStore;
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js
index ff0f1f2f8..de5653f04 100644
--- a/src/features/delayApp/Component.js
+++ b/src/features/delayApp/Component.js
@@ -4,29 +4,39 @@ import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
6 6
7import { Button } from '@meetfranz/forms';
7import { gaEvent } from '../../lib/analytics'; 8import { gaEvent } from '../../lib/analytics';
8 9
9import Button from '../../components/ui/Button'; 10// import Button from '../../components/ui/Button';
10 11
11import { config } from '.'; 12import { config } from '.';
12import styles from './styles'; 13import styles from './styles';
14import UserStore from '../../stores/UserStore';
13 15
14const messages = defineMessages({ 16const messages = defineMessages({
15 headline: { 17 headline: {
16 id: 'feature.delayApp.headline', 18 id: 'feature.delayApp.headline',
17 defaultMessage: '!!!Please purchase license to skip waiting', 19 defaultMessage: '!!!Please purchase license to skip waiting',
18 }, 20 },
21 headlineTrial: {
22 id: 'feature.delayApp.trial.headline',
23 defaultMessage: '!!!Get the free Franz Professional 14 day trial and skip the line',
24 },
19 action: { 25 action: {
20 id: 'feature.delayApp.action', 26 id: 'feature.delayApp.upgrade.action',
21 defaultMessage: '!!!Get a Franz Supporter License', 27 defaultMessage: '!!!Get a Franz Supporter License',
22 }, 28 },
29 actionTrial: {
30 id: 'feature.delayApp.trial.action',
31 defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional',
32 },
23 text: { 33 text: {
24 id: 'feature.delayApp.text', 34 id: 'feature.delayApp.text',
25 defaultMessage: '!!!Franz will continue in {seconds} seconds.', 35 defaultMessage: '!!!Franz will continue in {seconds} seconds.',
26 }, 36 },
27}); 37});
28 38
29export default @inject('actions') @injectSheet(styles) @observer class DelayApp extends Component { 39export default @inject('stores', 'actions') @injectSheet(styles) @observer class DelayApp extends Component {
30 static propTypes = { 40 static propTypes = {
31 // eslint-disable-next-line 41 // eslint-disable-next-line
32 classes: PropTypes.object.isRequired, 42 classes: PropTypes.object.isRequired,
@@ -62,25 +72,37 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
62 } 72 }
63 73
64 handleCTAClick() { 74 handleCTAClick() {
65 const { actions } = this.props; 75 const { actions, stores } = this.props;
76 const { hadSubscription } = stores.user.data;
77 const { defaultTrialPlan } = stores.features.features;
78
79 if (!hadSubscription) {
80 console.log('directly activate trial');
81 actions.user.activateTrial({ planId: defaultTrialPlan });
66 82
67 actions.ui.openSettings({ path: 'user' }); 83 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature');
84 } else {
85 actions.ui.openSettings({ path: 'user' });
68 86
69 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); 87 gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature');
88 }
70 } 89 }
71 90
72 render() { 91 render() {
73 const { classes } = this.props; 92 const { classes, stores } = this.props;
74 const { intl } = this.context; 93 const { intl } = this.context;
75 94
95 const { hadSubscription } = stores.user.data;
96
76 return ( 97 return (
77 <div className={`${classes.container}`}> 98 <div className={`${classes.container}`}>
78 <h1 className={classes.headline}>{intl.formatMessage(messages.headline)}</h1> 99 <h1 className={classes.headline}>{intl.formatMessage(hadSubscription ? messages.headline : messages.headlineTrial)}</h1>
79 <Button 100 <Button
80 label={intl.formatMessage(messages.action)} 101 label={intl.formatMessage(hadSubscription ? messages.action : messages.actionTrial)}
81 className={classes.button} 102 className={classes.button}
82 buttonType="inverted" 103 buttonType="inverted"
83 onClick={this.handleCTAClick.bind(this)} 104 onClick={this.handleCTAClick.bind(this)}
105 busy={stores.user.activateTrialRequest.isExecuting}
84 /> 106 />
85 <p className="footnote"> 107 <p className="footnote">
86 {intl.formatMessage(messages.text, { 108 {intl.formatMessage(messages.text, {
@@ -93,6 +115,9 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp
93} 115}
94 116
95DelayApp.wrappedComponent.propTypes = { 117DelayApp.wrappedComponent.propTypes = {
118 stores: PropTypes.shape({
119 user: PropTypes.instanceOf(UserStore).isRequired,
120 }).isRequired,
96 actions: PropTypes.shape({ 121 actions: PropTypes.shape({
97 ui: PropTypes.shape({ 122 ui: PropTypes.shape({
98 openSettings: PropTypes.func.isRequired, 123 openSettings: PropTypes.func.isRequired,
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 39fae3b20..627537de7 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -44,7 +44,7 @@ export default function init(stores) {
44 config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; 44 config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait;
45 45
46 autorun(() => { 46 autorun(() => {
47 if (stores.services.all.length === 0) { 47 if (stores.services.allDisplayed.length === 0) {
48 debug('seas', stores.services.all.length); 48 debug('seas', stores.services.all.length);
49 shownAfterLaunch = true; 49 shownAfterLaunch = true;
50 return; 50 return;
diff --git a/src/features/serviceLimit/components/LimitReachedInfobox.js b/src/features/serviceLimit/components/LimitReachedInfobox.js
new file mode 100644
index 000000000..fc54dcf85
--- /dev/null
+++ b/src/features/serviceLimit/components/LimitReachedInfobox.js
@@ -0,0 +1,78 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import { Infobox } from '@meetfranz/ui';
7
8import { gaEvent } from '../../../lib/analytics';
9
10const messages = defineMessages({
11 limitReached: {
12 id: 'feature.serviceLimit.limitReached',
13 defaultMessage: '!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.',
14 },
15 action: {
16 id: 'premiumFeature.button.upgradeAccount',
17 defaultMessage: '!!!Upgrade account',
18 },
19});
20
21const styles = theme => ({
22 container: {
23 height: 'auto',
24 background: theme.styleTypes.primary.accent,
25 color: theme.styleTypes.primary.contrast,
26 borderRadius: 0,
27 marginBottom: 0,
28
29 '& > div': {
30 marginBottom: 0,
31 },
32
33 '& button': {
34 color: theme.styleTypes.primary.contrast,
35 },
36 },
37});
38
39
40@inject('stores', 'actions') @injectSheet(styles) @observer
41class LimitReachedInfobox extends Component {
42 static propTypes = {
43 classes: PropTypes.object.isRequired,
44 stores: PropTypes.object.isRequired,
45 actions: PropTypes.object.isRequired,
46 };
47
48 static contextTypes = {
49 intl: intlShape,
50 };
51
52 render() {
53 const { classes, stores, actions } = this.props;
54 const { intl } = this.context;
55
56 const {
57 serviceLimit,
58 } = stores;
59
60 if (!serviceLimit.userHasReachedServiceLimit) return null;
61
62 return (
63 <Infobox
64 icon="mdiInformation"
65 className={classes.container}
66 ctaLabel={intl.formatMessage(messages.action)}
67 ctaOnClick={() => {
68 actions.ui.openSettings({ path: 'user' });
69 gaEvent('Service Limit', 'upgrade', 'Upgrade account');
70 }}
71 >
72 {intl.formatMessage(messages.limitReached, { amount: serviceLimit.serviceCount, limit: serviceLimit.serviceLimit })}
73 </Infobox>
74 );
75 }
76}
77
78export default LimitReachedInfobox;
diff --git a/src/features/serviceLimit/index.js b/src/features/serviceLimit/index.js
new file mode 100644
index 000000000..92ad8bb98
--- /dev/null
+++ b/src/features/serviceLimit/index.js
@@ -0,0 +1,33 @@
1import { reaction } from 'mobx';
2import { ServiceLimitStore } from './store';
3
4const debug = require('debug')('Franz:feature:serviceLimit');
5
6export const DEFAULT_SERVICE_LIMIT = 3;
7
8let store = null;
9
10export const serviceLimitStore = new ServiceLimitStore();
11
12export default function initServiceLimit(stores, actions) {
13 const { features } = stores;
14
15 // Toggle serviceLimit feature
16 reaction(
17 () => (
18 features.features.isServiceLimitEnabled
19 ),
20 (isEnabled) => {
21 if (isEnabled) {
22 debug('Initializing `serviceLimit` feature');
23 store = serviceLimitStore.start(stores, actions);
24 } else if (store) {
25 debug('Disabling `serviceLimit` feature');
26 serviceLimitStore.stop();
27 }
28 },
29 {
30 fireImmediately: true,
31 },
32 );
33}
diff --git a/src/features/serviceLimit/store.js b/src/features/serviceLimit/store.js
new file mode 100644
index 000000000..9836c5f51
--- /dev/null
+++ b/src/features/serviceLimit/store.js
@@ -0,0 +1,41 @@
1import { computed, observable } from 'mobx';
2import { FeatureStore } from '../utils/FeatureStore';
3import { DEFAULT_SERVICE_LIMIT } from '.';
4
5const debug = require('debug')('Franz:feature:serviceLimit:store');
6
7export class ServiceLimitStore extends FeatureStore {
8 @observable isServiceLimitEnabled = false;
9
10 start(stores, actions) {
11 debug('start');
12 this.stores = stores;
13 this.actions = actions;
14
15 this.isServiceLimitEnabled = true;
16 }
17
18 stop() {
19 super.stop();
20
21 this.isServiceLimitEnabled = false;
22 }
23
24 @computed get userHasReachedServiceLimit() {
25 if (!this.isServiceLimitEnabled) return false;
26
27 return this.serviceLimit !== 0 && this.serviceCount >= this.serviceLimit;
28 }
29
30 @computed get serviceLimit() {
31 if (!this.isServiceLimitEnabled || this.stores.features.features.serviceLimitCount === 0) return 0;
32
33 return this.stores.features.features.serviceLimitCount || DEFAULT_SERVICE_LIMIT;
34 }
35
36 @computed get serviceCount() {
37 return this.stores.services.all.length;
38 }
39}
40
41export default ServiceLimitStore;
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js
index 4bea327ad..55c600de4 100644
--- a/src/features/serviceProxy/index.js
+++ b/src/features/serviceProxy/index.js
@@ -9,17 +9,17 @@ const debug = require('debug')('Franz:feature:serviceProxy');
9 9
10export const config = observable({ 10export const config = observable({
11 isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled, 11 isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled,
12 isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature, 12 isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan,
13}); 13});
14 14
15export default function init(stores) { 15export default function init(stores) {
16 debug('Initializing `serviceProxy` feature'); 16 debug('Initializing `serviceProxy` feature');
17 17
18 autorun(() => { 18 autorun(() => {
19 const { isServiceProxyEnabled, isServiceProxyPremiumFeature } = stores.features.features; 19 const { isServiceProxyEnabled, isServiceProxyIncludedInCurrentPlan } = stores.features.features;
20 20
21 config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled; 21 config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled;
22 config.isPremium = isServiceProxyPremiumFeature !== undefined ? isServiceProxyPremiumFeature : DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature; 22 config.isIncludedInCurrentPlan = isServiceProxyIncludedInCurrentPlan !== undefined ? isServiceProxyIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan;
23 23
24 const services = stores.services.enabled; 24 const services = stores.services.enabled;
25 const isPremiumUser = stores.user.data.isPremium; 25 const isPremiumUser = stores.user.data.isPremium;
@@ -30,7 +30,7 @@ export default function init(stores) {
30 services.forEach((service) => { 30 services.forEach((service) => {
31 const s = session.fromPartition(`persist:service-${service.id}`); 31 const s = session.fromPartition(`persist:service-${service.id}`);
32 32
33 if (config.isEnabled && (isPremiumUser || !config.isPremium)) { 33 if (config.isEnabled && (isPremiumUser || !config.isIncludedInCurrentPlan)) {
34 const serviceProxyConfig = proxySettings[service.id]; 34 const serviceProxyConfig = proxySettings[service.id];
35 35
36 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) { 36 if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) {
diff --git a/src/features/shareFranz/Component.js b/src/features/shareFranz/Component.js
index 8d1d595c5..a33315e17 100644
--- a/src/features/shareFranz/Component.js
+++ b/src/features/shareFranz/Component.js
@@ -6,6 +6,9 @@ import { defineMessages, intlShape } from 'react-intl';
6import { Button } from '@meetfranz/forms'; 6import { Button } from '@meetfranz/forms';
7import { H1, Icon } from '@meetfranz/ui'; 7import { H1, Icon } from '@meetfranz/ui';
8 8
9import {
10 mdiHeart, mdiEmail, mdiFacebookBox, mdiTwitter,
11} from '@mdi/js';
9import Modal from '../../components/ui/Modal'; 12import Modal from '../../components/ui/Modal';
10import { state } from '.'; 13import { state } from '.';
11import { gaEvent } from '../../lib/analytics'; 14import { gaEvent } from '../../lib/analytics';
@@ -75,7 +78,7 @@ const styles = theme => ({
75 }, 78 },
76 cta: { 79 cta: {
77 background: theme.styleTypes.primary.contrast, 80 background: theme.styleTypes.primary.contrast,
78 color: theme.styleTypes.primary.accent, 81 color: `${theme.styleTypes.primary.accent} !important`,
79 82
80 '& svg': { 83 '& svg': {
81 fill: theme.styleTypes.primary.accent, 84 fill: theme.styleTypes.primary.accent,
@@ -116,7 +119,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz
116 close={this.close.bind(this)} 119 close={this.close.bind(this)}
117 > 120 >
118 <div className={classes.heartContainer}> 121 <div className={classes.heartContainer}>
119 <Icon icon="mdiHeart" className={classes.heart} size={4} /> 122 <Icon icon={mdiHeart} className={classes.heart} size={4} />
120 </div> 123 </div>
121 <H1 className={classes.headline}> 124 <H1 className={classes.headline}>
122 {intl.formatMessage(messages.headline)} 125 {intl.formatMessage(messages.headline)}
@@ -126,7 +129,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz
126 <Button 129 <Button
127 label={intl.formatMessage(messages.actionsEmail)} 130 label={intl.formatMessage(messages.actionsEmail)}
128 className={classes.cta} 131 className={classes.cta}
129 icon="mdiEmail" 132 icon={mdiEmail}
130 href={`mailto:?subject=Meet the cool app Franz&body=${intl.formatMessage(messages.shareTextEmail, { count: serviceCount })}}`} 133 href={`mailto:?subject=Meet the cool app Franz&body=${intl.formatMessage(messages.shareTextEmail, { count: serviceCount })}}`}
131 target="_blank" 134 target="_blank"
132 onClick={() => { 135 onClick={() => {
@@ -136,7 +139,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz
136 <Button 139 <Button
137 label={intl.formatMessage(messages.actionsFacebook)} 140 label={intl.formatMessage(messages.actionsFacebook)}
138 className={classes.cta} 141 className={classes.cta}
139 icon="mdiFacebookBox" 142 icon={mdiFacebookBox}
140 href="https://www.facebook.com/sharer/sharer.php?u=https://www.meetfranz.com?utm_source=facebook&utm_medium=referral&utm_campaign=share-button" 143 href="https://www.facebook.com/sharer/sharer.php?u=https://www.meetfranz.com?utm_source=facebook&utm_medium=referral&utm_campaign=share-button"
141 target="_blank" 144 target="_blank"
142 onClick={() => { 145 onClick={() => {
@@ -146,7 +149,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz
146 <Button 149 <Button
147 label={intl.formatMessage(messages.actionsTwitter)} 150 label={intl.formatMessage(messages.actionsTwitter)}
148 className={classes.cta} 151 className={classes.cta}
149 icon="mdiTwitter" 152 icon={mdiTwitter}
150 href={`http://twitter.com/intent/tweet?status=${intl.formatMessage(messages.shareTextTwitter, { count: serviceCount })}`} 153 href={`http://twitter.com/intent/tweet?status=${intl.formatMessage(messages.shareTextTwitter, { count: serviceCount })}`}
151 target="_blank" 154 target="_blank"
152 onClick={() => { 155 onClick={() => {
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js
index 79a2172b4..a07f9f63a 100644
--- a/src/features/spellchecker/index.js
+++ b/src/features/spellchecker/index.js
@@ -5,18 +5,18 @@ import { DEFAULT_FEATURES_CONFIG } from '../../config';
5const debug = require('debug')('Franz:feature:spellchecker'); 5const debug = require('debug')('Franz:feature:spellchecker');
6 6
7export const config = observable({ 7export const config = observable({
8 isPremium: DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature, 8 isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan,
9}); 9});
10 10
11export default function init(stores) { 11export default function init(stores) {
12 debug('Initializing `spellchecker` feature'); 12 debug('Initializing `spellchecker` feature');
13 13
14 autorun(() => { 14 autorun(() => {
15 const { isSpellcheckerPremiumFeature } = stores.features.features; 15 const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features;
16 16
17 config.isPremium = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature; 17 config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan;
18 18
19 if (!stores.user.data.isPremium && config.isPremium && stores.settings.app.enableSpellchecking) { 19 if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) {
20 debug('Override settings.spellcheckerEnabled flag to false'); 20 debug('Override settings.spellcheckerEnabled flag to false');
21 21
22 Object.assign(stores.settings.app, { 22 Object.assign(stores.settings.app, {
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js
index 9dd313109..143955a7b 100644
--- a/src/features/todos/components/TodosWebview.js
+++ b/src/features/todos/components/TodosWebview.js
@@ -4,12 +4,31 @@ import { observer } from 'mobx-react';
4import injectSheet from 'react-jss'; 4import injectSheet from 'react-jss';
5import Webview from 'react-electron-web-view'; 5import Webview from 'react-electron-web-view';
6import { Icon } from '@meetfranz/ui'; 6import { Icon } from '@meetfranz/ui';
7import { defineMessages, intlShape } from 'react-intl';
7 8
9import { mdiChevronRight, mdiCheckAll } from '@mdi/js';
8import * as environment from '../../../environment'; 10import * as environment from '../../../environment';
11import Appear from '../../../components/ui/effects/Appear';
12import ActivateTrialButton from '../../../components/ui/ActivateTrialButton';
9 13
10const OPEN_TODOS_BUTTON_SIZE = 45; 14const OPEN_TODOS_BUTTON_SIZE = 45;
11const CLOSE_TODOS_BUTTON_SIZE = 35; 15const CLOSE_TODOS_BUTTON_SIZE = 35;
12 16
17const messages = defineMessages({
18 premiumInfo: {
19 id: 'feature.todos.premium.info',
20 defaultMessage: '!!!The Franz Todos Preview is currently only available for Franz Premium accounts.',
21 },
22 upgradeCTA: {
23 id: 'feature.todos.premium.upgrade',
24 defaultMessage: '!!!Upgrade Account',
25 },
26 rolloutInfo: {
27 id: 'feature.todos.premium.rollout',
28 defaultMessage: '!!!Franz Todos will be available to everyone soon.',
29 },
30});
31
13const styles = theme => ({ 32const styles = theme => ({
14 root: { 33 root: {
15 background: theme.colorBackground, 34 background: theme.colorBackground,
@@ -47,7 +66,7 @@ const styles = theme => ({
47 height: OPEN_TODOS_BUTTON_SIZE, 66 height: OPEN_TODOS_BUTTON_SIZE,
48 background: theme.todos.toggleButton.background, 67 background: theme.todos.toggleButton.background,
49 position: 'absolute', 68 position: 'absolute',
50 bottom: 80, 69 bottom: 120,
51 right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)), 70 right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)),
52 borderRadius: OPEN_TODOS_BUTTON_SIZE / 2, 71 borderRadius: OPEN_TODOS_BUTTON_SIZE / 2,
53 opacity: props => (props.isVisible ? 0 : 1), 72 opacity: props => (props.isVisible ? 0 : 1),
@@ -71,10 +90,10 @@ const styles = theme => ({
71 height: CLOSE_TODOS_BUTTON_SIZE, 90 height: CLOSE_TODOS_BUTTON_SIZE,
72 background: theme.todos.toggleButton.background, 91 background: theme.todos.toggleButton.background,
73 position: 'absolute', 92 position: 'absolute',
74 bottom: 80, 93 bottom: 120,
75 right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2), 94 right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2),
76 borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2, 95 borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2,
77 opacity: 0, 96 opacity: ({ isTodosIncludedInCurrentPlan }) => (!isTodosIncludedInCurrentPlan ? 1 : 0),
78 transition: 'opacity 0.5s', 97 transition: 'opacity 0.5s',
79 zIndex: 600, 98 zIndex: 600,
80 display: 'flex', 99 display: 'flex',
@@ -86,6 +105,26 @@ const styles = theme => ({
86 fill: theme.todos.toggleButton.textColor, 105 fill: theme.todos.toggleButton.textColor,
87 }, 106 },
88 }, 107 },
108 premiumContainer: {
109 display: 'flex',
110 flexDirection: 'column',
111 justifyContent: 'center',
112 alignItems: 'center',
113 width: '80%',
114 maxWidth: 300,
115 margin: [-50, 'auto', 0],
116 textAlign: 'center',
117 },
118 premiumIcon: {
119 marginBottom: 40,
120 background: theme.styleTypes.primary.accent,
121 fill: theme.styleTypes.primary.contrast,
122 padding: 10,
123 borderRadius: 10,
124 },
125 premiumCTA: {
126 marginTop: 40,
127 },
89}); 128});
90 129
91@injectSheet(styles) @observer 130@injectSheet(styles) @observer
@@ -99,6 +138,7 @@ class TodosWebview extends Component {
99 resize: PropTypes.func.isRequired, 138 resize: PropTypes.func.isRequired,
100 width: PropTypes.number.isRequired, 139 width: PropTypes.number.isRequired,
101 minWidth: PropTypes.number.isRequired, 140 minWidth: PropTypes.number.isRequired,
141 isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired,
102 }; 142 };
103 143
104 state = { 144 state = {
@@ -106,6 +146,10 @@ class TodosWebview extends Component {
106 width: 300, 146 width: 300,
107 }; 147 };
108 148
149 static contextTypes = {
150 intl: intlShape,
151 };
152
109 componentWillMount() { 153 componentWillMount() {
110 const { width } = this.props; 154 const { width } = this.props;
111 155
@@ -182,9 +226,19 @@ class TodosWebview extends Component {
182 226
183 render() { 227 render() {
184 const { 228 const {
185 classes, isVisible, togglePanel, 229 classes,
230 isVisible,
231 togglePanel,
232 isTodosIncludedInCurrentPlan,
186 } = this.props; 233 } = this.props;
187 const { width, delta, isDragging } = this.state; 234
235 const {
236 width,
237 delta,
238 isDragging,
239 } = this.state;
240
241 const { intl } = this.context;
188 242
189 return ( 243 return (
190 <div 244 <div
@@ -198,7 +252,7 @@ class TodosWebview extends Component {
198 className={isVisible ? classes.closeTodosButton : classes.openTodosButton} 252 className={isVisible ? classes.closeTodosButton : classes.openTodosButton}
199 type="button" 253 type="button"
200 > 254 >
201 <Icon icon={isVisible ? 'mdiChevronRight' : 'mdiCheckAll'} size={2} /> 255 <Icon icon={isVisible ? mdiChevronRight : mdiCheckAll} size={2} />
202 </button> 256 </button>
203 <div 257 <div
204 className={classes.resizeHandler} 258 className={classes.resizeHandler}
@@ -211,18 +265,33 @@ class TodosWebview extends Component {
211 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad 265 style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad
212 /> 266 />
213 )} 267 )}
214 <Webview 268 {isTodosIncludedInCurrentPlan ? (
215 className={classes.webview} 269 <Webview
216 onDidAttach={() => { 270 className={classes.webview}
217 const { setTodosWebview } = this.props; 271 onDidAttach={() => {
218 setTodosWebview(this.webview); 272 const { setTodosWebview } = this.props;
219 this.startListeningToIpcMessages(); 273 setTodosWebview(this.webview);
220 }} 274 this.startListeningToIpcMessages();
221 partition="persist:todos" 275 }}
222 preload="./features/todos/preload.js" 276 partition="persist:todos"
223 ref={(webview) => { this.webview = webview ? webview.view : null; }} 277 preload="./features/todos/preload.js"
224 src={environment.TODOS_FRONTEND} 278 ref={(webview) => { this.webview = webview ? webview.view : null; }}
225 /> 279 src={environment.TODOS_FRONTEND}
280 />
281 ) : (
282 <Appear>
283 <div className={classes.premiumContainer}>
284 <Icon icon={mdiCheckAll} className={classes.premiumIcon} size={5} />
285 <p>{intl.formatMessage(messages.premiumInfo)}</p>
286 <p>{intl.formatMessage(messages.rolloutInfo)}</p>
287 <ActivateTrialButton
288 className={classes.premiumCTA}
289 gaEventInfo={{ category: 'Todos', event: 'upgrade' }}
290 short
291 />
292 </div>
293 </Appear>
294 )}
226 </div> 295 </div>
227 ); 296 );
228 } 297 }
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js
index d071d0677..65afc985b 100644
--- a/src/features/todos/containers/TodosScreen.js
+++ b/src/features/todos/containers/TodosScreen.js
@@ -1,12 +1,14 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import { observer } from 'mobx-react'; 2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
3 4
5import FeaturesStore from '../../../stores/FeaturesStore';
4import TodosWebview from '../components/TodosWebview'; 6import TodosWebview from '../components/TodosWebview';
5import ErrorBoundary from '../../../components/util/ErrorBoundary'; 7import ErrorBoundary from '../../../components/util/ErrorBoundary';
6import { TODOS_MIN_WIDTH, todosStore } from '..'; 8import { TODOS_MIN_WIDTH, todosStore } from '..';
7import { todoActions } from '../actions'; 9import { todoActions } from '../actions';
8 10
9@observer 11@inject('stores', 'actions') @observer
10class TodosScreen extends Component { 12class TodosScreen extends Component {
11 render() { 13 render() {
12 if (!todosStore || !todosStore.isFeatureActive) { 14 if (!todosStore || !todosStore.isFeatureActive) {
@@ -23,6 +25,7 @@ class TodosScreen extends Component {
23 width={todosStore.width} 25 width={todosStore.width}
24 minWidth={TODOS_MIN_WIDTH} 26 minWidth={TODOS_MIN_WIDTH}
25 resize={width => todoActions.resize({ width })} 27 resize={width => todoActions.resize({ width })}
28 isTodosIncludedInCurrentPlan={this.props.stores.features.features.isTodosIncludedInCurrentPlan || false}
26 /> 29 />
27 </ErrorBoundary> 30 </ErrorBoundary>
28 ); 31 );
@@ -30,3 +33,9 @@ class TodosScreen extends Component {
30} 33}
31 34
32export default TodosScreen; 35export default TodosScreen;
36
37TodosScreen.wrappedComponent.propTypes = {
38 stores: PropTypes.shape({
39 features: PropTypes.instanceOf(FeaturesStore).isRequired,
40 }).isRequired,
41};
diff --git a/src/features/todos/store.js b/src/features/todos/store.js
index 242b38bf7..170408ebb 100644
--- a/src/features/todos/store.js
+++ b/src/features/todos/store.js
@@ -12,6 +12,7 @@ import { createReactions } from '../../stores/lib/Reaction';
12import { createActionBindings } from '../utils/ActionBinding'; 12import { createActionBindings } from '../utils/ActionBinding';
13import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.'; 13import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.';
14import { IPC } from './constants'; 14import { IPC } from './constants';
15import { state as delayAppState } from '../delayApp';
15 16
16const debug = require('debug')('Franz:feature:todos:store'); 17const debug = require('debug')('Franz:feature:todos:store');
17 18
@@ -29,6 +30,7 @@ export default class TodoStore extends FeatureStore {
29 } 30 }
30 31
31 @computed get isTodosPanelVisible() { 32 @computed get isTodosPanelVisible() {
33 if (this.stores.services.all.length === 0 || delayAppState.isDelayAppScreenVisible) return false;
32 if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE; 34 if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE;
33 35
34 return this.settings.isTodosPanelVisible; 36 return this.settings.isTodosPanelVisible;
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js
index 684e50dd0..e7bc0b157 100644
--- a/src/features/workspaces/components/WorkspaceDrawer.js
+++ b/src/features/workspaces/components/WorkspaceDrawer.js
@@ -7,6 +7,7 @@ import { H1, Icon, ProBadge } from '@meetfranz/ui';
7import { Button } from '@meetfranz/forms/lib'; 7import { Button } from '@meetfranz/forms/lib';
8import ReactTooltip from 'react-tooltip'; 8import ReactTooltip from 'react-tooltip';
9 9
10import { mdiPlusBox, mdiSettings } from '@mdi/js';
10import WorkspaceDrawerItem from './WorkspaceDrawerItem'; 11import WorkspaceDrawerItem from './WorkspaceDrawerItem';
11import { workspaceActions } from '../actions'; 12import { workspaceActions } from '../actions';
12import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index'; 13import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index';
@@ -159,7 +160,7 @@ class WorkspaceDrawer extends Component {
159 data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`} 160 data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`}
160 > 161 >
161 <Icon 162 <Icon
162 icon="mdiSettings" 163 icon={mdiSettings}
163 size={1.5} 164 size={1.5}
164 className={classes.workspacesSettingsButtonIcon} 165 className={classes.workspacesSettingsButtonIcon}
165 /> 166 />
@@ -184,7 +185,7 @@ class WorkspaceDrawer extends Component {
184 className={classes.premiumCtaButton} 185 className={classes.premiumCtaButton}
185 buttonType="primary" 186 buttonType="primary"
186 label={intl.formatMessage(messages.premiumCtaButtonLabel)} 187 label={intl.formatMessage(messages.premiumCtaButtonLabel)}
187 icon="mdiPlusBox" 188 icon={mdiPlusBox}
188 onClick={() => { 189 onClick={() => {
189 workspaceActions.openWorkspaceSettings(); 190 workspaceActions.openWorkspaceSettings();
190 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta'); 191 gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta');
@@ -227,7 +228,7 @@ class WorkspaceDrawer extends Component {
227 }} 228 }}
228 > 229 >
229 <Icon 230 <Icon
230 icon="mdiPlusBox" 231 icon={mdiPlusBox}
231 size={1} 232 size={1}
232 className={classes.workspacesSettingsButtonIcon} 233 className={classes.workspacesSettingsButtonIcon}
233 /> 234 />
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
index c4a800a7b..a70d1d66f 100644
--- a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
+++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js
@@ -21,11 +21,8 @@ const styles = theme => ({
21 alignItems: 'flex-start', 21 alignItems: 'flex-start',
22 position: 'absolute', 22 position: 'absolute',
23 transition: 'width 0.5s ease', 23 transition: 'width 0.5s ease',
24 width: '100%',
25 marginTop: '20px',
26 },
27 wrapperWhenDrawerIsOpen: {
28 width: `calc(100% - ${theme.workspaces.drawer.width}px)`, 24 width: `calc(100% - ${theme.workspaces.drawer.width}px)`,
25 marginTop: '20px',
29 }, 26 },
30 component: { 27 component: {
31 background: 'rgba(20, 20, 20, 0.4)', 28 background: 'rgba(20, 20, 20, 0.4)',
@@ -64,14 +61,13 @@ class WorkspaceSwitchingIndicator extends Component {
64 render() { 61 render() {
65 const { classes, theme } = this.props; 62 const { classes, theme } = this.props;
66 const { intl } = this.context; 63 const { intl } = this.context;
67 const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore; 64 const { isSwitchingWorkspace, nextWorkspace } = workspaceStore;
68 if (!isSwitchingWorkspace) return null; 65 if (!isSwitchingWorkspace) return null;
69 const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; 66 const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services';
70 return ( 67 return (
71 <div 68 <div
72 className={classnames([ 69 className={classnames([
73 classes.wrapper, 70 classes.wrapper,
74 isWorkspaceDrawerOpen ? classes.wrapperWhenDrawerIsOpen : null,
75 ])} 71 ])}
76 > 72 >
77 <div className={classes.component}> 73 <div className={classes.component}>
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js
index 09c98ab8c..059a681de 100644
--- a/src/features/workspaces/components/WorkspacesDashboard.js
+++ b/src/features/workspaces/components/WorkspacesDashboard.js
@@ -1,6 +1,6 @@
1import React, { Component, Fragment } from 'react'; 1import React, { Component, Fragment } 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, inject } 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';
6import { Infobox } from '@meetfranz/ui'; 6import { Infobox } from '@meetfranz/ui';
@@ -12,6 +12,8 @@ import Request from '../../../stores/lib/Request';
12import Appear from '../../../components/ui/effects/Appear'; 12import Appear from '../../../components/ui/effects/Appear';
13import { workspaceStore } from '../index'; 13import { workspaceStore } from '../index';
14import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; 14import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer';
15import UIStore from '../../../stores/UIStore';
16import ActivateTrialButton from '../../../components/ui/ActivateTrialButton';
15 17
16const messages = defineMessages({ 18const messages = defineMessages({
17 headline: { 19 headline: {
@@ -62,17 +64,27 @@ const styles = theme => ({
62 height: 'auto', 64 height: 'auto',
63 }, 65 },
64 premiumAnnouncement: { 66 premiumAnnouncement: {
65 padding: '20px', 67 padding: 20,
66 backgroundColor: '#3498db', 68 // backgroundColor: '#3498db',
67 marginLeft: '-20px', 69 marginLeft: -20,
68 marginBottom: '20px', 70 marginBottom: 40,
71 paddingBottom: 40,
69 height: 'auto', 72 height: 'auto',
70 color: 'white', 73 display: 'flex',
71 borderRadius: theme.borderRadius, 74 borderBottom: [1, 'solid', theme.inputBackground],
75 },
76 teaserImage: {
77 width: 200,
78 height: '100%',
79 float: 'left',
80 margin: [-8, 0, 0, -20],
81 },
82 upgradeCTA: {
83 marginTop: 20,
72 }, 84 },
73}); 85});
74 86
75@injectSheet(styles) @observer 87@inject('stores') @injectSheet(styles) @observer
76class WorkspacesDashboard extends Component { 88class WorkspacesDashboard extends Component {
77 static propTypes = { 89 static propTypes = {
78 classes: PropTypes.object.isRequired, 90 classes: PropTypes.object.isRequired,
@@ -100,7 +112,9 @@ class WorkspacesDashboard extends Component {
100 onWorkspaceClick, 112 onWorkspaceClick,
101 workspaces, 113 workspaces,
102 } = this.props; 114 } = this.props;
115
103 const { intl } = this.context; 116 const { intl } = this.context;
117
104 return ( 118 return (
105 <div className="settings__main"> 119 <div className="settings__main">
106 <div className="settings__header"> 120 <div className="settings__header">
@@ -138,13 +152,21 @@ class WorkspacesDashboard extends Component {
138 152
139 {workspaceStore.isPremiumUpgradeRequired && ( 153 {workspaceStore.isPremiumUpgradeRequired && (
140 <div className={classes.premiumAnnouncement}> 154 <div className={classes.premiumAnnouncement}>
141 <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2> 155 <img src={`./assets/images/workspaces/teaser_${this.props.stores.ui.isDarkThemeActive ? 'dark' : 'light'}.png`} className={classes.teaserImage} alt="" />
142 <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p> 156 <div>
157 <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2>
158 <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p>
159 <ActivateTrialButton
160 className={classes.upgradeCTA}
161 gaEventInfo={{ category: 'Workspaces', event: 'upgrade' }}
162 short
163 />
164 </div>
143 </div> 165 </div>
144 )} 166 )}
145 167
146 <PremiumFeatureContainer 168 <PremiumFeatureContainer
147 condition={workspaceStore.isPremiumFeature} 169 condition={() => workspaceStore.isPremiumUpgradeRequired}
148 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }} 170 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }}
149 > 171 >
150 {/* ===== Create workspace form ===== */} 172 {/* ===== Create workspace form ===== */}
@@ -207,3 +229,9 @@ class WorkspacesDashboard extends Component {
207} 229}
208 230
209export default WorkspacesDashboard; 231export default WorkspacesDashboard;
232
233WorkspacesDashboard.wrappedComponent.propTypes = {
234 stores: PropTypes.shape({
235 ui: PropTypes.instanceOf(UIStore).isRequired,
236 }).isRequired,
237};
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js
index a82f6895c..4a1f80b4e 100644
--- a/src/features/workspaces/store.js
+++ b/src/features/workspaces/store.js
@@ -253,11 +253,10 @@ export default class WorkspacesStore extends FeatureStore {
253 }; 253 };
254 254
255 _setIsPremiumFeatureReaction = () => { 255 _setIsPremiumFeatureReaction = () => {
256 const { features, user } = this.stores; 256 const { features } = this.stores;
257 const { isPremium } = user.data; 257 const { isWorkspaceIncludedInCurrentPlan } = features.features;
258 const { isWorkspacePremiumFeature } = features.features; 258 this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan;
259 this.isPremiumFeature = isWorkspacePremiumFeature; 259 this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan;
260 this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium;
261 }; 260 };
262 261
263 _setWorkspaceBeingEditedReaction = () => { 262 _setWorkspaceBeingEditedReaction = () => {
diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js
new file mode 100644
index 000000000..e0f1fd89a
--- /dev/null
+++ b/src/helpers/plan-helpers.js
@@ -0,0 +1,45 @@
1import { defineMessages } from 'react-intl';
2import { PLANS_MAPPING, PLANS } from '../config';
3
4const messages = defineMessages({
5 [PLANS.PRO]: {
6 id: 'pricing.plan.pro',
7 defaultMessage: '!!!Franz Professional',
8 },
9 [PLANS.PERSONAL]: {
10 id: 'pricing.plan.personal',
11 defaultMessage: '!!!Franz Personal',
12 },
13 [PLANS.FREE]: {
14 id: 'pricing.plan.free',
15 defaultMessage: '!!!Franz Free',
16 },
17 [PLANS.LEGACY]: {
18 id: 'pricing.plan.legacy',
19 defaultMessage: '!!!Franz Premium',
20 },
21});
22
23export function i18nPlanName(planId, intl) {
24 if (!planId) {
25 throw new Error('planId is required');
26 }
27
28 if (!intl) {
29 throw new Error('intl context is required');
30 }
31
32 const plan = PLANS_MAPPING[planId];
33
34 return intl.formatMessage(messages[plan]);
35}
36
37export function getPlan(planId) {
38 if (!planId) {
39 throw new Error('planId is required');
40 }
41
42 const plan = PLANS_MAPPING[planId];
43
44 return plan;
45}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json
index 5959fb059..367184c01 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 ],
@@ -477,156 +542,143 @@
477 "defaultMessage": "!!!Sign up", 542 "defaultMessage": "!!!Sign up",
478 "end": { 543 "end": {
479 "column": 3, 544 "column": 3,
480 "line": 21 545 "line": 20
481 }, 546 },
482 "file": "src/components/auth/Signup.js", 547 "file": "src/components/auth/Signup.js",
483 "id": "signup.headline", 548 "id": "signup.headline",
484 "start": { 549 "start": {
485 "column": 12, 550 "column": 12,
486 "line": 18 551 "line": 17
487 } 552 }
488 }, 553 },
489 { 554 {
490 "defaultMessage": "!!!Firstname", 555 "defaultMessage": "!!!Firstname",
491 "end": { 556 "end": {
492 "column": 3, 557 "column": 3,
493 "line": 25 558 "line": 24
494 }, 559 },
495 "file": "src/components/auth/Signup.js", 560 "file": "src/components/auth/Signup.js",
496 "id": "signup.firstname.label", 561 "id": "signup.firstname.label",
497 "start": { 562 "start": {
498 "column": 18, 563 "column": 18,
499 "line": 22 564 "line": 21
500 } 565 }
501 }, 566 },
502 { 567 {
503 "defaultMessage": "!!!Lastname", 568 "defaultMessage": "!!!Lastname",
504 "end": { 569 "end": {
505 "column": 3, 570 "column": 3,
506 "line": 29 571 "line": 28
507 }, 572 },
508 "file": "src/components/auth/Signup.js", 573 "file": "src/components/auth/Signup.js",
509 "id": "signup.lastname.label", 574 "id": "signup.lastname.label",
510 "start": { 575 "start": {
511 "column": 17, 576 "column": 17,
512 "line": 26 577 "line": 25
513 } 578 }
514 }, 579 },
515 { 580 {
516 "defaultMessage": "!!!Email address", 581 "defaultMessage": "!!!Email address",
517 "end": { 582 "end": {
518 "column": 3, 583 "column": 3,
519 "line": 33 584 "line": 32
520 }, 585 },
521 "file": "src/components/auth/Signup.js", 586 "file": "src/components/auth/Signup.js",
522 "id": "signup.email.label", 587 "id": "signup.email.label",
523 "start": { 588 "start": {
524 "column": 14, 589 "column": 14,
525 "line": 30 590 "line": 29
526 }
527 },
528 {
529 "defaultMessage": "!!!Company",
530 "end": {
531 "column": 3,
532 "line": 37
533 },
534 "file": "src/components/auth/Signup.js",
535 "id": "signup.company.label",
536 "start": {
537 "column": 16,
538 "line": 34
539 } 591 }
540 }, 592 },
541 { 593 {
542 "defaultMessage": "!!!Password", 594 "defaultMessage": "!!!Password",
543 "end": { 595 "end": {
544 "column": 3, 596 "column": 3,
545 "line": 41 597 "line": 40
546 }, 598 },
547 "file": "src/components/auth/Signup.js", 599 "file": "src/components/auth/Signup.js",
548 "id": "signup.password.label", 600 "id": "signup.password.label",
549 "start": { 601 "start": {
550 "column": 17, 602 "column": 17,
551 "line": 38 603 "line": 37
552 } 604 }
553 }, 605 },
554 { 606 {
555 "defaultMessage": "!!!By creating a Franz account you accept the", 607 "defaultMessage": "!!!By creating a Franz account you accept the",
556 "end": { 608 "end": {
557 "column": 3, 609 "column": 3,
558 "line": 45 610 "line": 44
559 }, 611 },
560 "file": "src/components/auth/Signup.js", 612 "file": "src/components/auth/Signup.js",
561 "id": "signup.legal.info", 613 "id": "signup.legal.info",
562 "start": { 614 "start": {
563 "column": 13, 615 "column": 13,
564 "line": 42 616 "line": 41
565 } 617 }
566 }, 618 },
567 { 619 {
568 "defaultMessage": "!!!Terms of service", 620 "defaultMessage": "!!!Terms of service",
569 "end": { 621 "end": {
570 "column": 3, 622 "column": 3,
571 "line": 49 623 "line": 48
572 }, 624 },
573 "file": "src/components/auth/Signup.js", 625 "file": "src/components/auth/Signup.js",
574 "id": "signup.legal.terms", 626 "id": "signup.legal.terms",
575 "start": { 627 "start": {
576 "column": 9, 628 "column": 9,
577 "line": 46 629 "line": 45
578 } 630 }
579 }, 631 },
580 { 632 {
581 "defaultMessage": "!!!Privacy Statement", 633 "defaultMessage": "!!!Privacy Statement",
582 "end": { 634 "end": {
583 "column": 3, 635 "column": 3,
584 "line": 53 636 "line": 52
585 }, 637 },
586 "file": "src/components/auth/Signup.js", 638 "file": "src/components/auth/Signup.js",
587 "id": "signup.legal.privacy", 639 "id": "signup.legal.privacy",
588 "start": { 640 "start": {
589 "column": 11, 641 "column": 11,
590 "line": 50 642 "line": 49
591 } 643 }
592 }, 644 },
593 { 645 {
594 "defaultMessage": "!!!Create account", 646 "defaultMessage": "!!!Create account",
595 "end": { 647 "end": {
596 "column": 3, 648 "column": 3,
597 "line": 57 649 "line": 56
598 }, 650 },
599 "file": "src/components/auth/Signup.js", 651 "file": "src/components/auth/Signup.js",
600 "id": "signup.submit.label", 652 "id": "signup.submit.label",
601 "start": { 653 "start": {
602 "column": 21, 654 "column": 21,
603 "line": 54 655 "line": 53
604 } 656 }
605 }, 657 },
606 { 658 {
607 "defaultMessage": "!!!Already have an account, sign in?", 659 "defaultMessage": "!!!Already have an account, sign in?",
608 "end": { 660 "end": {
609 "column": 3, 661 "column": 3,
610 "line": 61 662 "line": 60
611 }, 663 },
612 "file": "src/components/auth/Signup.js", 664 "file": "src/components/auth/Signup.js",
613 "id": "signup.link.login", 665 "id": "signup.link.login",
614 "start": { 666 "start": {
615 "column": 13, 667 "column": 13,
616 "line": 58 668 "line": 57
617 } 669 }
618 }, 670 },
619 { 671 {
620 "defaultMessage": "!!!A user with that email address already exists", 672 "defaultMessage": "!!!A user with that email address already exists",
621 "end": { 673 "end": {
622 "column": 3, 674 "column": 3,
623 "line": 65 675 "line": 64
624 }, 676 },
625 "file": "src/components/auth/Signup.js", 677 "file": "src/components/auth/Signup.js",
626 "id": "signup.emailDuplicate", 678 "id": "signup.emailDuplicate",
627 "start": { 679 "start": {
628 "column": 18, 680 "column": 18,
629 "line": 62 681 "line": 61
630 } 682 }
631 } 683 }
632 ], 684 ],
@@ -669,39 +721,39 @@
669 "defaultMessage": "!!!Your services have been updated.", 721 "defaultMessage": "!!!Your services have been updated.",
670 "end": { 722 "end": {
671 "column": 3, 723 "column": 3,
672 "line": 30 724 "line": 31
673 }, 725 },
674 "file": "src/components/layout/AppLayout.js", 726 "file": "src/components/layout/AppLayout.js",
675 "id": "infobar.servicesUpdated", 727 "id": "infobar.servicesUpdated",
676 "start": { 728 "start": {
677 "column": 19, 729 "column": 19,
678 "line": 27 730 "line": 28
679 } 731 }
680 }, 732 },
681 { 733 {
682 "defaultMessage": "!!!Reload services", 734 "defaultMessage": "!!!Reload services",
683 "end": { 735 "end": {
684 "column": 3, 736 "column": 3,
685 "line": 34 737 "line": 35
686 }, 738 },
687 "file": "src/components/layout/AppLayout.js", 739 "file": "src/components/layout/AppLayout.js",
688 "id": "infobar.buttonReloadServices", 740 "id": "infobar.buttonReloadServices",
689 "start": { 741 "start": {
690 "column": 24, 742 "column": 24,
691 "line": 31 743 "line": 32
692 } 744 }
693 }, 745 },
694 { 746 {
695 "defaultMessage": "!!!Could not load services and user information", 747 "defaultMessage": "!!!Could not load services and user information",
696 "end": { 748 "end": {
697 "column": 3, 749 "column": 3,
698 "line": 38 750 "line": 39
699 }, 751 },
700 "file": "src/components/layout/AppLayout.js", 752 "file": "src/components/layout/AppLayout.js",
701 "id": "infobar.requiredRequestsFailed", 753 "id": "infobar.requiredRequestsFailed",
702 "start": { 754 "start": {
703 "column": 26, 755 "column": 26,
704 "line": 35 756 "line": 36
705 } 757 }
706 } 758 }
707 ], 759 ],
@@ -894,29 +946,99 @@
894 { 946 {
895 "descriptors": [ 947 "descriptors": [
896 { 948 {
897 "defaultMessage": "!!!Welcome to Franz", 949 "defaultMessage": "!!!You have reached your service limit.",
898 "end": { 950 "end": {
899 "column": 3, 951 "column": 3,
900 "line": 14 952 "line": 14
901 }, 953 },
954 "file": "src/components/services/content/ServiceRestricted.js",
955 "id": "service.restrictedHandler.serviceLimit.headline",
956 "start": {
957 "column": 24,
958 "line": 11
959 }
960 },
961 {
962 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
963 "end": {
964 "column": 3,
965 "line": 18
966 },
967 "file": "src/components/services/content/ServiceRestricted.js",
968 "id": "service.restrictedHandler.serviceLimit.text",
969 "start": {
970 "column": 20,
971 "line": 15
972 }
973 },
974 {
975 "defaultMessage": "!!!Franz Professional Plan required",
976 "end": {
977 "column": 3,
978 "line": 22
979 },
980 "file": "src/components/services/content/ServiceRestricted.js",
981 "id": "service.restrictedHandler.customUrl.headline",
982 "start": {
983 "column": 21,
984 "line": 19
985 }
986 },
987 {
988 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
989 "end": {
990 "column": 3,
991 "line": 26
992 },
993 "file": "src/components/services/content/ServiceRestricted.js",
994 "id": "service.restrictedHandler.customUrl.text",
995 "start": {
996 "column": 17,
997 "line": 23
998 }
999 },
1000 {
1001 "defaultMessage": "!!!Upgrade Account",
1002 "end": {
1003 "column": 3,
1004 "line": 30
1005 },
1006 "file": "src/components/services/content/ServiceRestricted.js",
1007 "id": "service.restrictedHandler.action",
1008 "start": {
1009 "column": 10,
1010 "line": 27
1011 }
1012 }
1013 ],
1014 "path": "src/components/services/content/ServiceRestricted.json"
1015 },
1016 {
1017 "descriptors": [
1018 {
1019 "defaultMessage": "!!!Welcome to Franz",
1020 "end": {
1021 "column": 3,
1022 "line": 17
1023 },
902 "file": "src/components/services/content/Services.js", 1024 "file": "src/components/services/content/Services.js",
903 "id": "services.welcome", 1025 "id": "services.welcome",
904 "start": { 1026 "start": {
905 "column": 11, 1027 "column": 11,
906 "line": 11 1028 "line": 14
907 } 1029 }
908 }, 1030 },
909 { 1031 {
910 "defaultMessage": "!!!Get started", 1032 "defaultMessage": "!!!Get started",
911 "end": { 1033 "end": {
912 "column": 3, 1034 "column": 3,
913 "line": 18 1035 "line": 21
914 }, 1036 },
915 "file": "src/components/services/content/Services.js", 1037 "file": "src/components/services/content/Services.js",
916 "id": "services.getStarted", 1038 "id": "services.getStarted",
917 "start": { 1039 "start": {
918 "column": 14, 1040 "column": 14,
919 "line": 15 1041 "line": 18
920 } 1042 }
921 } 1043 }
922 ], 1044 ],
@@ -1107,38 +1229,25 @@
1107 "defaultMessage": "!!!Account", 1229 "defaultMessage": "!!!Account",
1108 "end": { 1230 "end": {
1109 "column": 3, 1231 "column": 3,
1110 "line": 17 1232 "line": 21
1111 }, 1233 },
1112 "file": "src/components/settings/account/AccountDashboard.js", 1234 "file": "src/components/settings/account/AccountDashboard.js",
1113 "id": "settings.account.headline", 1235 "id": "settings.account.headline",
1114 "start": { 1236 "start": {
1115 "column": 12, 1237 "column": 12,
1116 "line": 14 1238 "line": 18
1117 } 1239 }
1118 }, 1240 },
1119 { 1241 {
1120 "defaultMessage": "!!!Your Subscription", 1242 "defaultMessage": "!!!Your Subscription",
1121 "end": { 1243 "end": {
1122 "column": 3, 1244 "column": 3,
1123 "line": 21 1245 "line": 25
1124 }, 1246 },
1125 "file": "src/components/settings/account/AccountDashboard.js", 1247 "file": "src/components/settings/account/AccountDashboard.js",
1126 "id": "settings.account.headlineSubscription", 1248 "id": "settings.account.headlineSubscription",
1127 "start": { 1249 "start": {
1128 "column": 24, 1250 "column": 24,
1129 "line": 18
1130 }
1131 },
1132 {
1133 "defaultMessage": "!!!Upgrade your Account",
1134 "end": {
1135 "column": 3,
1136 "line": 25
1137 },
1138 "file": "src/components/settings/account/AccountDashboard.js",
1139 "id": "settings.account.headlineUpgrade",
1140 "start": {
1141 "column": 19,
1142 "line": 22 1251 "line": 22
1143 } 1252 }
1144 }, 1253 },
@@ -1169,133 +1278,198 @@
1169 } 1278 }
1170 }, 1279 },
1171 { 1280 {
1172 "defaultMessage": "!!!Basic Account", 1281 "defaultMessage": "!!!Upgrade to Franz Professional",
1173 "end": { 1282 "end": {
1174 "column": 3, 1283 "column": 3,
1175 "line": 37 1284 "line": 37
1176 }, 1285 },
1177 "file": "src/components/settings/account/AccountDashboard.js", 1286 "file": "src/components/settings/account/AccountDashboard.js",
1287 "id": "settings.account.upgradeToPro.label",
1288 "start": {
1289 "column": 23,
1290 "line": 34
1291 }
1292 },
1293 {
1294 "defaultMessage": "!!!Basic Account",
1295 "end": {
1296 "column": 3,
1297 "line": 41
1298 },
1299 "file": "src/components/settings/account/AccountDashboard.js",
1178 "id": "settings.account.accountType.basic", 1300 "id": "settings.account.accountType.basic",
1179 "start": { 1301 "start": {
1180 "column": 20, 1302 "column": 20,
1181 "line": 34 1303 "line": 38
1182 } 1304 }
1183 }, 1305 },
1184 { 1306 {
1185 "defaultMessage": "!!!Premium Supporter Account", 1307 "defaultMessage": "!!!Premium Supporter Account",
1186 "end": { 1308 "end": {
1187 "column": 3, 1309 "column": 3,
1188 "line": 41 1310 "line": 45
1189 }, 1311 },
1190 "file": "src/components/settings/account/AccountDashboard.js", 1312 "file": "src/components/settings/account/AccountDashboard.js",
1191 "id": "settings.account.accountType.premium", 1313 "id": "settings.account.accountType.premium",
1192 "start": { 1314 "start": {
1193 "column": 22, 1315 "column": 22,
1194 "line": 38 1316 "line": 42
1195 } 1317 }
1196 }, 1318 },
1197 { 1319 {
1198 "defaultMessage": "!!!Edit Account", 1320 "defaultMessage": "!!!Edit Account",
1199 "end": { 1321 "end": {
1200 "column": 3, 1322 "column": 3,
1201 "line": 45 1323 "line": 49
1202 }, 1324 },
1203 "file": "src/components/settings/account/AccountDashboard.js", 1325 "file": "src/components/settings/account/AccountDashboard.js",
1204 "id": "settings.account.account.editButton", 1326 "id": "settings.account.account.editButton",
1205 "start": { 1327 "start": {
1206 "column": 21, 1328 "column": 21,
1207 "line": 42 1329 "line": 46
1208 } 1330 }
1209 }, 1331 },
1210 { 1332 {
1211 "defaultMessage": "!!Invoices", 1333 "defaultMessage": "!!Invoices",
1212 "end": { 1334 "end": {
1213 "column": 3, 1335 "column": 3,
1214 "line": 49 1336 "line": 53
1215 }, 1337 },
1216 "file": "src/components/settings/account/AccountDashboard.js", 1338 "file": "src/components/settings/account/AccountDashboard.js",
1217 "id": "settings.account.headlineInvoices", 1339 "id": "settings.account.headlineInvoices",
1218 "start": { 1340 "start": {
1219 "column": 18, 1341 "column": 18,
1220 "line": 46 1342 "line": 50
1221 } 1343 }
1222 }, 1344 },
1223 { 1345 {
1224 "defaultMessage": "!!!Download", 1346 "defaultMessage": "!!!Download",
1225 "end": { 1347 "end": {
1226 "column": 3, 1348 "column": 3,
1227 "line": 53 1349 "line": 57
1228 }, 1350 },
1229 "file": "src/components/settings/account/AccountDashboard.js", 1351 "file": "src/components/settings/account/AccountDashboard.js",
1230 "id": "settings.account.invoiceDownload", 1352 "id": "settings.account.invoiceDownload",
1231 "start": { 1353 "start": {
1232 "column": 19, 1354 "column": 19,
1233 "line": 50 1355 "line": 54
1234 } 1356 }
1235 }, 1357 },
1236 { 1358 {
1237 "defaultMessage": "!!!Could not load user information", 1359 "defaultMessage": "!!!Could not load user information",
1238 "end": { 1360 "end": {
1239 "column": 3, 1361 "column": 3,
1240 "line": 57 1362 "line": 61
1241 }, 1363 },
1242 "file": "src/components/settings/account/AccountDashboard.js", 1364 "file": "src/components/settings/account/AccountDashboard.js",
1243 "id": "settings.account.userInfoRequestFailed", 1365 "id": "settings.account.userInfoRequestFailed",
1244 "start": { 1366 "start": {
1245 "column": 25, 1367 "column": 25,
1246 "line": 54 1368 "line": 58
1247 } 1369 }
1248 }, 1370 },
1249 { 1371 {
1250 "defaultMessage": "!!!Try again", 1372 "defaultMessage": "!!!Try again",
1251 "end": { 1373 "end": {
1252 "column": 3, 1374 "column": 3,
1253 "line": 61 1375 "line": 65
1254 }, 1376 },
1255 "file": "src/components/settings/account/AccountDashboard.js", 1377 "file": "src/components/settings/account/AccountDashboard.js",
1256 "id": "settings.account.tryReloadUserInfoRequest", 1378 "id": "settings.account.tryReloadUserInfoRequest",
1257 "start": { 1379 "start": {
1258 "column": 28, 1380 "column": 28,
1259 "line": 58 1381 "line": 62
1260 } 1382 }
1261 }, 1383 },
1262 { 1384 {
1263 "defaultMessage": "!!!Delete account", 1385 "defaultMessage": "!!!Delete account",
1264 "end": { 1386 "end": {
1265 "column": 3, 1387 "column": 3,
1266 "line": 65 1388 "line": 69
1267 }, 1389 },
1268 "file": "src/components/settings/account/AccountDashboard.js", 1390 "file": "src/components/settings/account/AccountDashboard.js",
1269 "id": "settings.account.deleteAccount", 1391 "id": "settings.account.deleteAccount",
1270 "start": { 1392 "start": {
1271 "column": 17, 1393 "column": 17,
1272 "line": 62 1394 "line": 66
1273 } 1395 }
1274 }, 1396 },
1275 { 1397 {
1276 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.", 1398 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.",
1277 "end": { 1399 "end": {
1278 "column": 3, 1400 "column": 3,
1279 "line": 69 1401 "line": 73
1280 }, 1402 },
1281 "file": "src/components/settings/account/AccountDashboard.js", 1403 "file": "src/components/settings/account/AccountDashboard.js",
1282 "id": "settings.account.deleteInfo", 1404 "id": "settings.account.deleteInfo",
1283 "start": { 1405 "start": {
1284 "column": 14, 1406 "column": 14,
1285 "line": 66 1407 "line": 70
1286 } 1408 }
1287 }, 1409 },
1288 { 1410 {
1289 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", 1411 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!",
1290 "end": { 1412 "end": {
1291 "column": 3, 1413 "column": 3,
1292 "line": 73 1414 "line": 77
1293 }, 1415 },
1294 "file": "src/components/settings/account/AccountDashboard.js", 1416 "file": "src/components/settings/account/AccountDashboard.js",
1295 "id": "settings.account.deleteEmailSent", 1417 "id": "settings.account.deleteEmailSent",
1296 "start": { 1418 "start": {
1297 "column": 19, 1419 "column": 19,
1298 "line": 70 1420 "line": 74
1421 }
1422 },
1423 {
1424 "defaultMessage": "!!!Free Trial",
1425 "end": {
1426 "column": 3,
1427 "line": 81
1428 },
1429 "file": "src/components/settings/account/AccountDashboard.js",
1430 "id": "settings.account.trial",
1431 "start": {
1432 "column": 9,
1433 "line": 78
1434 }
1435 },
1436 {
1437 "defaultMessage": "!!!Your Franz License:",
1438 "end": {
1439 "column": 3,
1440 "line": 85
1441 },
1442 "file": "src/components/settings/account/AccountDashboard.js",
1443 "id": "settings.account.yourLicense",
1444 "start": {
1445 "column": 15,
1446 "line": 82
1447 }
1448 },
1449 {
1450 "defaultMessage": "!!!Your free trial ends in {duration}.",
1451 "end": {
1452 "column": 3,
1453 "line": 89
1454 },
1455 "file": "src/components/settings/account/AccountDashboard.js",
1456 "id": "settings.account.trialEndsIn",
1457 "start": {
1458 "column": 15,
1459 "line": 86
1460 }
1461 },
1462 {
1463 "defaultMessage": "!!!Please update your billing info to continue using {license} after your trial period.",
1464 "end": {
1465 "column": 3,
1466 "line": 93
1467 },
1468 "file": "src/components/settings/account/AccountDashboard.js",
1469 "id": "settings.account.trialUpdateBillingInfo",
1470 "start": {
1471 "column": 33,
1472 "line": 90
1299 } 1473 }
1300 } 1474 }
1301 ], 1475 ],
@@ -1307,104 +1481,104 @@
1307 "defaultMessage": "!!!Available services", 1481 "defaultMessage": "!!!Available services",
1308 "end": { 1482 "end": {
1309 "column": 3, 1483 "column": 3,
1310 "line": 16 1484 "line": 17
1311 }, 1485 },
1312 "file": "src/components/settings/navigation/SettingsNavigation.js", 1486 "file": "src/components/settings/navigation/SettingsNavigation.js",
1313 "id": "settings.navigation.availableServices", 1487 "id": "settings.navigation.availableServices",
1314 "start": { 1488 "start": {
1315 "column": 21, 1489 "column": 21,
1316 "line": 13 1490 "line": 14
1317 } 1491 }
1318 }, 1492 },
1319 { 1493 {
1320 "defaultMessage": "!!!Your services", 1494 "defaultMessage": "!!!Your services",
1321 "end": { 1495 "end": {
1322 "column": 3, 1496 "column": 3,
1323 "line": 20 1497 "line": 21
1324 }, 1498 },
1325 "file": "src/components/settings/navigation/SettingsNavigation.js", 1499 "file": "src/components/settings/navigation/SettingsNavigation.js",
1326 "id": "settings.navigation.yourServices", 1500 "id": "settings.navigation.yourServices",
1327 "start": { 1501 "start": {
1328 "column": 16, 1502 "column": 16,
1329 "line": 17 1503 "line": 18
1330 } 1504 }
1331 }, 1505 },
1332 { 1506 {
1333 "defaultMessage": "!!!Your workspaces", 1507 "defaultMessage": "!!!Your workspaces",
1334 "end": { 1508 "end": {
1335 "column": 3, 1509 "column": 3,
1336 "line": 24 1510 "line": 25
1337 }, 1511 },
1338 "file": "src/components/settings/navigation/SettingsNavigation.js", 1512 "file": "src/components/settings/navigation/SettingsNavigation.js",
1339 "id": "settings.navigation.yourWorkspaces", 1513 "id": "settings.navigation.yourWorkspaces",
1340 "start": { 1514 "start": {
1341 "column": 18, 1515 "column": 18,
1342 "line": 21 1516 "line": 22
1343 } 1517 }
1344 }, 1518 },
1345 { 1519 {
1346 "defaultMessage": "!!!Account", 1520 "defaultMessage": "!!!Account",
1347 "end": { 1521 "end": {
1348 "column": 3, 1522 "column": 3,
1349 "line": 28 1523 "line": 29
1350 }, 1524 },
1351 "file": "src/components/settings/navigation/SettingsNavigation.js", 1525 "file": "src/components/settings/navigation/SettingsNavigation.js",
1352 "id": "settings.navigation.account", 1526 "id": "settings.navigation.account",
1353 "start": { 1527 "start": {
1354 "column": 11, 1528 "column": 11,
1355 "line": 25 1529 "line": 26
1356 } 1530 }
1357 }, 1531 },
1358 { 1532 {
1359 "defaultMessage": "!!!Manage Team", 1533 "defaultMessage": "!!!Manage Team",
1360 "end": { 1534 "end": {
1361 "column": 3, 1535 "column": 3,
1362 "line": 32 1536 "line": 33
1363 }, 1537 },
1364 "file": "src/components/settings/navigation/SettingsNavigation.js", 1538 "file": "src/components/settings/navigation/SettingsNavigation.js",
1365 "id": "settings.navigation.team", 1539 "id": "settings.navigation.team",
1366 "start": { 1540 "start": {
1367 "column": 8, 1541 "column": 8,
1368 "line": 29 1542 "line": 30
1369 } 1543 }
1370 }, 1544 },
1371 { 1545 {
1372 "defaultMessage": "!!!Settings", 1546 "defaultMessage": "!!!Settings",
1373 "end": { 1547 "end": {
1374 "column": 3, 1548 "column": 3,
1375 "line": 36 1549 "line": 37
1376 }, 1550 },
1377 "file": "src/components/settings/navigation/SettingsNavigation.js", 1551 "file": "src/components/settings/navigation/SettingsNavigation.js",
1378 "id": "settings.navigation.settings", 1552 "id": "settings.navigation.settings",
1379 "start": { 1553 "start": {
1380 "column": 12, 1554 "column": 12,
1381 "line": 33 1555 "line": 34
1382 } 1556 }
1383 }, 1557 },
1384 { 1558 {
1385 "defaultMessage": "!!!Invite Friends", 1559 "defaultMessage": "!!!Invite Friends",
1386 "end": { 1560 "end": {
1387 "column": 3, 1561 "column": 3,
1388 "line": 40 1562 "line": 41
1389 }, 1563 },
1390 "file": "src/components/settings/navigation/SettingsNavigation.js", 1564 "file": "src/components/settings/navigation/SettingsNavigation.js",
1391 "id": "settings.navigation.inviteFriends", 1565 "id": "settings.navigation.inviteFriends",
1392 "start": { 1566 "start": {
1393 "column": 17, 1567 "column": 17,
1394 "line": 37 1568 "line": 38
1395 } 1569 }
1396 }, 1570 },
1397 { 1571 {
1398 "defaultMessage": "!!!Logout", 1572 "defaultMessage": "!!!Logout",
1399 "end": { 1573 "end": {
1400 "column": 3, 1574 "column": 3,
1401 "line": 44 1575 "line": 45
1402 }, 1576 },
1403 "file": "src/components/settings/navigation/SettingsNavigation.js", 1577 "file": "src/components/settings/navigation/SettingsNavigation.js",
1404 "id": "settings.navigation.logout", 1578 "id": "settings.navigation.logout",
1405 "start": { 1579 "start": {
1406 "column": 10, 1580 "column": 10,
1407 "line": 41 1581 "line": 42
1408 } 1582 }
1409 } 1583 }
1410 ], 1584 ],
@@ -1416,104 +1590,182 @@
1416 "defaultMessage": "!!!Available Services", 1590 "defaultMessage": "!!!Available Services",
1417 "end": { 1591 "end": {
1418 "column": 3, 1592 "column": 3,
1419 "line": 18 1593 "line": 23
1420 }, 1594 },
1421 "file": "src/components/settings/recipes/RecipesDashboard.js", 1595 "file": "src/components/settings/recipes/RecipesDashboard.js",
1422 "id": "settings.recipes.headline", 1596 "id": "settings.recipes.headline",
1423 "start": { 1597 "start": {
1424 "column": 12, 1598 "column": 12,
1425 "line": 15 1599 "line": 20
1426 } 1600 }
1427 }, 1601 },
1428 { 1602 {
1429 "defaultMessage": "!!!Search service", 1603 "defaultMessage": "!!!Search service",
1430 "end": { 1604 "end": {
1431 "column": 3, 1605 "column": 3,
1432 "line": 22 1606 "line": 27
1433 }, 1607 },
1434 "file": "src/components/settings/recipes/RecipesDashboard.js", 1608 "file": "src/components/settings/recipes/RecipesDashboard.js",
1435 "id": "settings.searchService", 1609 "id": "settings.searchService",
1436 "start": { 1610 "start": {
1437 "column": 17, 1611 "column": 17,
1438 "line": 19 1612 "line": 24
1439 } 1613 }
1440 }, 1614 },
1441 { 1615 {
1442 "defaultMessage": "!!!Most popular", 1616 "defaultMessage": "!!!Most popular",
1443 "end": { 1617 "end": {
1444 "column": 3, 1618 "column": 3,
1445 "line": 26 1619 "line": 31
1446 }, 1620 },
1447 "file": "src/components/settings/recipes/RecipesDashboard.js", 1621 "file": "src/components/settings/recipes/RecipesDashboard.js",
1448 "id": "settings.recipes.mostPopular", 1622 "id": "settings.recipes.mostPopular",
1449 "start": { 1623 "start": {
1450 "column": 22, 1624 "column": 22,
1451 "line": 23 1625 "line": 28
1452 } 1626 }
1453 }, 1627 },
1454 { 1628 {
1455 "defaultMessage": "!!!All services", 1629 "defaultMessage": "!!!All services",
1456 "end": { 1630 "end": {
1457 "column": 3, 1631 "column": 3,
1458 "line": 30 1632 "line": 35
1459 }, 1633 },
1460 "file": "src/components/settings/recipes/RecipesDashboard.js", 1634 "file": "src/components/settings/recipes/RecipesDashboard.js",
1461 "id": "settings.recipes.all", 1635 "id": "settings.recipes.all",
1462 "start": { 1636 "start": {
1463 "column": 14, 1637 "column": 14,
1464 "line": 27 1638 "line": 32
1465 } 1639 }
1466 }, 1640 },
1467 { 1641 {
1468 "defaultMessage": "!!!Development", 1642 "defaultMessage": "!!!Custom Services",
1469 "end": { 1643 "end": {
1470 "column": 3, 1644 "column": 3,
1471 "line": 34 1645 "line": 39
1472 }, 1646 },
1473 "file": "src/components/settings/recipes/RecipesDashboard.js", 1647 "file": "src/components/settings/recipes/RecipesDashboard.js",
1474 "id": "settings.recipes.dev", 1648 "id": "settings.recipes.custom",
1475 "start": { 1649 "start": {
1476 "column": 14, 1650 "column": 17,
1477 "line": 31 1651 "line": 36
1478 } 1652 }
1479 }, 1653 },
1480 { 1654 {
1481 "defaultMessage": "!!!Sorry, but no service matched your search term.", 1655 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1482 "end": { 1656 "end": {
1483 "column": 3, 1657 "column": 3,
1484 "line": 38 1658 "line": 43
1485 }, 1659 },
1486 "file": "src/components/settings/recipes/RecipesDashboard.js", 1660 "file": "src/components/settings/recipes/RecipesDashboard.js",
1487 "id": "settings.recipes.nothingFound", 1661 "id": "settings.recipes.nothingFound",
1488 "start": { 1662 "start": {
1489 "column": 16, 1663 "column": 16,
1490 "line": 35 1664 "line": 40
1491 } 1665 }
1492 }, 1666 },
1493 { 1667 {
1494 "defaultMessage": "!!!Service successfully added", 1668 "defaultMessage": "!!!Service successfully added",
1495 "end": { 1669 "end": {
1496 "column": 3, 1670 "column": 3,
1497 "line": 42 1671 "line": 47
1498 }, 1672 },
1499 "file": "src/components/settings/recipes/RecipesDashboard.js", 1673 "file": "src/components/settings/recipes/RecipesDashboard.js",
1500 "id": "settings.recipes.servicesSuccessfulAddedInfo", 1674 "id": "settings.recipes.servicesSuccessfulAddedInfo",
1501 "start": { 1675 "start": {
1502 "column": 31, 1676 "column": 31,
1503 "line": 39 1677 "line": 44
1504 } 1678 }
1505 }, 1679 },
1506 { 1680 {
1507 "defaultMessage": "!!!Missing a service?", 1681 "defaultMessage": "!!!Missing a service?",
1508 "end": { 1682 "end": {
1509 "column": 3, 1683 "column": 3,
1510 "line": 46 1684 "line": 51
1511 }, 1685 },
1512 "file": "src/components/settings/recipes/RecipesDashboard.js", 1686 "file": "src/components/settings/recipes/RecipesDashboard.js",
1513 "id": "settings.recipes.missingService", 1687 "id": "settings.recipes.missingService",
1514 "start": { 1688 "start": {
1515 "column": 18, 1689 "column": 18,
1516 "line": 43 1690 "line": 48
1691 }
1692 },
1693 {
1694 "defaultMessage": "!!!To add a custom service, copy the recipe folder into:",
1695 "end": {
1696 "column": 3,
1697 "line": 55
1698 },
1699 "file": "src/components/settings/recipes/RecipesDashboard.js",
1700 "id": "settings.recipes.customService.intro",
1701 "start": {
1702 "column": 21,
1703 "line": 52
1704 }
1705 },
1706 {
1707 "defaultMessage": "!!!Open directory",
1708 "end": {
1709 "column": 3,
1710 "line": 59
1711 },
1712 "file": "src/components/settings/recipes/RecipesDashboard.js",
1713 "id": "settings.recipes.customService.openFolder",
1714 "start": {
1715 "column": 14,
1716 "line": 56
1717 }
1718 },
1719 {
1720 "defaultMessage": "!!!Developer Documentation",
1721 "end": {
1722 "column": 3,
1723 "line": 63
1724 },
1725 "file": "src/components/settings/recipes/RecipesDashboard.js",
1726 "id": "settings.recipes.customService.openDevDocs",
1727 "start": {
1728 "column": 15,
1729 "line": 60
1730 }
1731 },
1732 {
1733 "defaultMessage": "!!!Custom Service Recipes",
1734 "end": {
1735 "column": 3,
1736 "line": 67
1737 },
1738 "file": "src/components/settings/recipes/RecipesDashboard.js",
1739 "id": "settings.recipes.customService.headline.customRecipes",
1740 "start": {
1741 "column": 25,
1742 "line": 64
1743 }
1744 },
1745 {
1746 "defaultMessage": "!!!Community Services",
1747 "end": {
1748 "column": 3,
1749 "line": 71
1750 },
1751 "file": "src/components/settings/recipes/RecipesDashboard.js",
1752 "id": "settings.recipes.customService.headline.communityRecipes",
1753 "start": {
1754 "column": 28,
1755 "line": 68
1756 }
1757 },
1758 {
1759 "defaultMessage": "!!!Your Development Service Recipes",
1760 "end": {
1761 "column": 3,
1762 "line": 75
1763 },
1764 "file": "src/components/settings/recipes/RecipesDashboard.js",
1765 "id": "settings.recipes.customService.headline.devRecipes",
1766 "start": {
1767 "column": 22,
1768 "line": 72
1517 } 1769 }
1518 } 1770 }
1519 ], 1771 ],
@@ -1525,286 +1777,286 @@
1525 "defaultMessage": "!!!Save service", 1777 "defaultMessage": "!!!Save service",
1526 "end": { 1778 "end": {
1527 "column": 3, 1779 "column": 3,
1528 "line": 25 1780 "line": 27
1529 }, 1781 },
1530 "file": "src/components/settings/services/EditServiceForm.js", 1782 "file": "src/components/settings/services/EditServiceForm.js",
1531 "id": "settings.service.form.saveButton", 1783 "id": "settings.service.form.saveButton",
1532 "start": { 1784 "start": {
1533 "column": 15, 1785 "column": 15,
1534 "line": 22 1786 "line": 24
1535 } 1787 }
1536 }, 1788 },
1537 { 1789 {
1538 "defaultMessage": "!!!Delete Service", 1790 "defaultMessage": "!!!Delete Service",
1539 "end": { 1791 "end": {
1540 "column": 3, 1792 "column": 3,
1541 "line": 29 1793 "line": 31
1542 }, 1794 },
1543 "file": "src/components/settings/services/EditServiceForm.js", 1795 "file": "src/components/settings/services/EditServiceForm.js",
1544 "id": "settings.service.form.deleteButton", 1796 "id": "settings.service.form.deleteButton",
1545 "start": { 1797 "start": {
1546 "column": 17, 1798 "column": 17,
1547 "line": 26 1799 "line": 28
1548 } 1800 }
1549 }, 1801 },
1550 { 1802 {
1551 "defaultMessage": "!!!Available services", 1803 "defaultMessage": "!!!Available services",
1552 "end": { 1804 "end": {
1553 "column": 3, 1805 "column": 3,
1554 "line": 33 1806 "line": 35
1555 }, 1807 },
1556 "file": "src/components/settings/services/EditServiceForm.js", 1808 "file": "src/components/settings/services/EditServiceForm.js",
1557 "id": "settings.service.form.availableServices", 1809 "id": "settings.service.form.availableServices",
1558 "start": { 1810 "start": {
1559 "column": 21, 1811 "column": 21,
1560 "line": 30 1812 "line": 32
1561 } 1813 }
1562 }, 1814 },
1563 { 1815 {
1564 "defaultMessage": "!!!Your services", 1816 "defaultMessage": "!!!Your services",
1565 "end": { 1817 "end": {
1566 "column": 3, 1818 "column": 3,
1567 "line": 37 1819 "line": 39
1568 }, 1820 },
1569 "file": "src/components/settings/services/EditServiceForm.js", 1821 "file": "src/components/settings/services/EditServiceForm.js",
1570 "id": "settings.service.form.yourServices", 1822 "id": "settings.service.form.yourServices",
1571 "start": { 1823 "start": {
1572 "column": 16, 1824 "column": 16,
1573 "line": 34 1825 "line": 36
1574 } 1826 }
1575 }, 1827 },
1576 { 1828 {
1577 "defaultMessage": "!!!Add {name}", 1829 "defaultMessage": "!!!Add {name}",
1578 "end": { 1830 "end": {
1579 "column": 3, 1831 "column": 3,
1580 "line": 41 1832 "line": 43
1581 }, 1833 },
1582 "file": "src/components/settings/services/EditServiceForm.js", 1834 "file": "src/components/settings/services/EditServiceForm.js",
1583 "id": "settings.service.form.addServiceHeadline", 1835 "id": "settings.service.form.addServiceHeadline",
1584 "start": { 1836 "start": {
1585 "column": 22, 1837 "column": 22,
1586 "line": 38 1838 "line": 40
1587 } 1839 }
1588 }, 1840 },
1589 { 1841 {
1590 "defaultMessage": "!!!Edit {name}", 1842 "defaultMessage": "!!!Edit {name}",
1591 "end": { 1843 "end": {
1592 "column": 3, 1844 "column": 3,
1593 "line": 45 1845 "line": 47
1594 }, 1846 },
1595 "file": "src/components/settings/services/EditServiceForm.js", 1847 "file": "src/components/settings/services/EditServiceForm.js",
1596 "id": "settings.service.form.editServiceHeadline", 1848 "id": "settings.service.form.editServiceHeadline",
1597 "start": { 1849 "start": {
1598 "column": 23, 1850 "column": 23,
1599 "line": 42 1851 "line": 44
1600 } 1852 }
1601 }, 1853 },
1602 { 1854 {
1603 "defaultMessage": "!!!Hosted", 1855 "defaultMessage": "!!!Hosted",
1604 "end": { 1856 "end": {
1605 "column": 3, 1857 "column": 3,
1606 "line": 49 1858 "line": 51
1607 }, 1859 },
1608 "file": "src/components/settings/services/EditServiceForm.js", 1860 "file": "src/components/settings/services/EditServiceForm.js",
1609 "id": "settings.service.form.tabHosted", 1861 "id": "settings.service.form.tabHosted",
1610 "start": { 1862 "start": {
1611 "column": 13, 1863 "column": 13,
1612 "line": 46 1864 "line": 48
1613 } 1865 }
1614 }, 1866 },
1615 { 1867 {
1616 "defaultMessage": "!!!Self hosted ⭐️", 1868 "defaultMessage": "!!!Self hosted ⭐️",
1617 "end": { 1869 "end": {
1618 "column": 3, 1870 "column": 3,
1619 "line": 53 1871 "line": 55
1620 }, 1872 },
1621 "file": "src/components/settings/services/EditServiceForm.js", 1873 "file": "src/components/settings/services/EditServiceForm.js",
1622 "id": "settings.service.form.tabOnPremise", 1874 "id": "settings.service.form.tabOnPremise",
1623 "start": { 1875 "start": {
1624 "column": 16, 1876 "column": 16,
1625 "line": 50 1877 "line": 52
1626 } 1878 }
1627 }, 1879 },
1628 { 1880 {
1629 "defaultMessage": "!!!Use the hosted {name} service.", 1881 "defaultMessage": "!!!Use the hosted {name} service.",
1630 "end": { 1882 "end": {
1631 "column": 3, 1883 "column": 3,
1632 "line": 57 1884 "line": 59
1633 }, 1885 },
1634 "file": "src/components/settings/services/EditServiceForm.js", 1886 "file": "src/components/settings/services/EditServiceForm.js",
1635 "id": "settings.service.form.useHostedService", 1887 "id": "settings.service.form.useHostedService",
1636 "start": { 1888 "start": {
1637 "column": 20, 1889 "column": 20,
1638 "line": 54 1890 "line": 56
1639 } 1891 }
1640 }, 1892 },
1641 { 1893 {
1642 "defaultMessage": "!!!Could not validate custom {name} server.", 1894 "defaultMessage": "!!!Could not validate custom {name} server.",
1643 "end": { 1895 "end": {
1644 "column": 3, 1896 "column": 3,
1645 "line": 61 1897 "line": 63
1646 }, 1898 },
1647 "file": "src/components/settings/services/EditServiceForm.js", 1899 "file": "src/components/settings/services/EditServiceForm.js",
1648 "id": "settings.service.form.customUrlValidationError", 1900 "id": "settings.service.form.customUrlValidationError",
1649 "start": { 1901 "start": {
1650 "column": 28, 1902 "column": 28,
1651 "line": 58 1903 "line": 60
1652 } 1904 }
1653 }, 1905 },
1654 { 1906 {
1655 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", 1907 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.",
1656 "end": { 1908 "end": {
1657 "column": 3, 1909 "column": 3,
1658 "line": 65 1910 "line": 67
1659 }, 1911 },
1660 "file": "src/components/settings/services/EditServiceForm.js", 1912 "file": "src/components/settings/services/EditServiceForm.js",
1661 "id": "settings.service.form.customUrlPremiumInfo", 1913 "id": "settings.service.form.customUrlPremiumInfo",
1662 "start": { 1914 "start": {
1663 "column": 24, 1915 "column": 24,
1664 "line": 62 1916 "line": 64
1665 } 1917 }
1666 }, 1918 },
1667 { 1919 {
1668 "defaultMessage": "!!!Upgrade your account", 1920 "defaultMessage": "!!!Upgrade your account",
1669 "end": { 1921 "end": {
1670 "column": 3, 1922 "column": 3,
1671 "line": 69 1923 "line": 71
1672 }, 1924 },
1673 "file": "src/components/settings/services/EditServiceForm.js", 1925 "file": "src/components/settings/services/EditServiceForm.js",
1674 "id": "settings.service.form.customUrlUpgradeAccount", 1926 "id": "settings.service.form.customUrlUpgradeAccount",
1675 "start": { 1927 "start": {
1676 "column": 27, 1928 "column": 27,
1677 "line": 66 1929 "line": 68
1678 } 1930 }
1679 }, 1931 },
1680 { 1932 {
1681 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 1933 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
1682 "end": { 1934 "end": {
1683 "column": 3, 1935 "column": 3,
1684 "line": 73 1936 "line": 75
1685 }, 1937 },
1686 "file": "src/components/settings/services/EditServiceForm.js", 1938 "file": "src/components/settings/services/EditServiceForm.js",
1687 "id": "settings.service.form.indirectMessageInfo", 1939 "id": "settings.service.form.indirectMessageInfo",
1688 "start": { 1940 "start": {
1689 "column": 23, 1941 "column": 23,
1690 "line": 70 1942 "line": 72
1691 } 1943 }
1692 }, 1944 },
1693 { 1945 {
1694 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 1946 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
1695 "end": { 1947 "end": {
1696 "column": 3, 1948 "column": 3,
1697 "line": 77 1949 "line": 79
1698 }, 1950 },
1699 "file": "src/components/settings/services/EditServiceForm.js", 1951 "file": "src/components/settings/services/EditServiceForm.js",
1700 "id": "settings.service.form.isMutedInfo", 1952 "id": "settings.service.form.isMutedInfo",
1701 "start": { 1953 "start": {
1702 "column": 15, 1954 "column": 15,
1703 "line": 74 1955 "line": 76
1704 } 1956 }
1705 }, 1957 },
1706 { 1958 {
1707 "defaultMessage": "!!!Notifications", 1959 "defaultMessage": "!!!Notifications",
1708 "end": { 1960 "end": {
1709 "column": 3, 1961 "column": 3,
1710 "line": 81 1962 "line": 83
1711 }, 1963 },
1712 "file": "src/components/settings/services/EditServiceForm.js", 1964 "file": "src/components/settings/services/EditServiceForm.js",
1713 "id": "settings.service.form.headlineNotifications", 1965 "id": "settings.service.form.headlineNotifications",
1714 "start": { 1966 "start": {
1715 "column": 25, 1967 "column": 25,
1716 "line": 78 1968 "line": 80
1717 } 1969 }
1718 }, 1970 },
1719 { 1971 {
1720 "defaultMessage": "!!!Unread message badges", 1972 "defaultMessage": "!!!Unread message badges",
1721 "end": { 1973 "end": {
1722 "column": 3, 1974 "column": 3,
1723 "line": 85 1975 "line": 87
1724 }, 1976 },
1725 "file": "src/components/settings/services/EditServiceForm.js", 1977 "file": "src/components/settings/services/EditServiceForm.js",
1726 "id": "settings.service.form.headlineBadges", 1978 "id": "settings.service.form.headlineBadges",
1727 "start": { 1979 "start": {
1728 "column": 18, 1980 "column": 18,
1729 "line": 82 1981 "line": 84
1730 } 1982 }
1731 }, 1983 },
1732 { 1984 {
1733 "defaultMessage": "!!!General", 1985 "defaultMessage": "!!!General",
1734 "end": { 1986 "end": {
1735 "column": 3, 1987 "column": 3,
1736 "line": 89 1988 "line": 91
1737 }, 1989 },
1738 "file": "src/components/settings/services/EditServiceForm.js", 1990 "file": "src/components/settings/services/EditServiceForm.js",
1739 "id": "settings.service.form.headlineGeneral", 1991 "id": "settings.service.form.headlineGeneral",
1740 "start": { 1992 "start": {
1741 "column": 19, 1993 "column": 19,
1742 "line": 86 1994 "line": 88
1743 } 1995 }
1744 }, 1996 },
1745 { 1997 {
1746 "defaultMessage": "!!!Delete", 1998 "defaultMessage": "!!!Delete",
1747 "end": { 1999 "end": {
1748 "column": 3, 2000 "column": 3,
1749 "line": 93 2001 "line": 95
1750 }, 2002 },
1751 "file": "src/components/settings/services/EditServiceForm.js", 2003 "file": "src/components/settings/services/EditServiceForm.js",
1752 "id": "settings.service.form.iconDelete", 2004 "id": "settings.service.form.iconDelete",
1753 "start": { 2005 "start": {
1754 "column": 14, 2006 "column": 14,
1755 "line": 90 2007 "line": 92
1756 } 2008 }
1757 }, 2009 },
1758 { 2010 {
1759 "defaultMessage": "!!!Drop your image, or click here", 2011 "defaultMessage": "!!!Drop your image, or click here",
1760 "end": { 2012 "end": {
1761 "column": 3, 2013 "column": 3,
1762 "line": 97 2014 "line": 99
1763 }, 2015 },
1764 "file": "src/components/settings/services/EditServiceForm.js", 2016 "file": "src/components/settings/services/EditServiceForm.js",
1765 "id": "settings.service.form.iconUpload", 2017 "id": "settings.service.form.iconUpload",
1766 "start": { 2018 "start": {
1767 "column": 14, 2019 "column": 14,
1768 "line": 94 2020 "line": 96
1769 } 2021 }
1770 }, 2022 },
1771 { 2023 {
1772 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 2024 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
1773 "end": { 2025 "end": {
1774 "column": 3, 2026 "column": 3,
1775 "line": 101 2027 "line": 103
1776 }, 2028 },
1777 "file": "src/components/settings/services/EditServiceForm.js", 2029 "file": "src/components/settings/services/EditServiceForm.js",
1778 "id": "settings.service.form.proxy.headline", 2030 "id": "settings.service.form.proxy.headline",
1779 "start": { 2031 "start": {
1780 "column": 17, 2032 "column": 17,
1781 "line": 98 2033 "line": 100
1782 } 2034 }
1783 }, 2035 },
1784 { 2036 {
1785 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", 2037 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.",
1786 "end": { 2038 "end": {
1787 "column": 3, 2039 "column": 3,
1788 "line": 105 2040 "line": 107
1789 }, 2041 },
1790 "file": "src/components/settings/services/EditServiceForm.js", 2042 "file": "src/components/settings/services/EditServiceForm.js",
1791 "id": "settings.service.form.proxy.restartInfo", 2043 "id": "settings.service.form.proxy.restartInfo",
1792 "start": { 2044 "start": {
1793 "column": 20, 2045 "column": 20,
1794 "line": 102 2046 "line": 104
1795 } 2047 }
1796 }, 2048 },
1797 { 2049 {
1798 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", 2050 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.",
1799 "end": { 2051 "end": {
1800 "column": 3, 2052 "column": 3,
1801 "line": 109 2053 "line": 111
1802 }, 2054 },
1803 "file": "src/components/settings/services/EditServiceForm.js", 2055 "file": "src/components/settings/services/EditServiceForm.js",
1804 "id": "settings.service.form.proxy.info", 2056 "id": "settings.service.form.proxy.info",
1805 "start": { 2057 "start": {
1806 "column": 13, 2058 "column": 13,
1807 "line": 106 2059 "line": 108
1808 } 2060 }
1809 } 2061 }
1810 ], 2062 ],
@@ -1917,117 +2169,117 @@
1917 "defaultMessage": "!!!Your services", 2169 "defaultMessage": "!!!Your services",
1918 "end": { 2170 "end": {
1919 "column": 3, 2171 "column": 3,
1920 "line": 17 2172 "line": 18
1921 }, 2173 },
1922 "file": "src/components/settings/services/ServicesDashboard.js", 2174 "file": "src/components/settings/services/ServicesDashboard.js",
1923 "id": "settings.services.headline", 2175 "id": "settings.services.headline",
1924 "start": { 2176 "start": {
1925 "column": 12, 2177 "column": 12,
1926 "line": 14 2178 "line": 15
1927 } 2179 }
1928 }, 2180 },
1929 { 2181 {
1930 "defaultMessage": "!!!Search service", 2182 "defaultMessage": "!!!Search service",
1931 "end": { 2183 "end": {
1932 "column": 3, 2184 "column": 3,
1933 "line": 21 2185 "line": 22
1934 }, 2186 },
1935 "file": "src/components/settings/services/ServicesDashboard.js", 2187 "file": "src/components/settings/services/ServicesDashboard.js",
1936 "id": "settings.searchService", 2188 "id": "settings.searchService",
1937 "start": { 2189 "start": {
1938 "column": 17, 2190 "column": 17,
1939 "line": 18 2191 "line": 19
1940 } 2192 }
1941 }, 2193 },
1942 { 2194 {
1943 "defaultMessage": "!!!You haven't added any services yet.", 2195 "defaultMessage": "!!!You haven't added any services yet.",
1944 "end": { 2196 "end": {
1945 "column": 3, 2197 "column": 3,
1946 "line": 25 2198 "line": 26
1947 }, 2199 },
1948 "file": "src/components/settings/services/ServicesDashboard.js", 2200 "file": "src/components/settings/services/ServicesDashboard.js",
1949 "id": "settings.services.noServicesAdded", 2201 "id": "settings.services.noServicesAdded",
1950 "start": { 2202 "start": {
1951 "column": 19, 2203 "column": 19,
1952 "line": 22 2204 "line": 23
1953 } 2205 }
1954 }, 2206 },
1955 { 2207 {
1956 "defaultMessage": "!!!Sorry, but no service matched your search term.", 2208 "defaultMessage": "!!!Sorry, but no service matched your search term.",
1957 "end": { 2209 "end": {
1958 "column": 3, 2210 "column": 3,
1959 "line": 29 2211 "line": 30
1960 }, 2212 },
1961 "file": "src/components/settings/services/ServicesDashboard.js", 2213 "file": "src/components/settings/services/ServicesDashboard.js",
1962 "id": "settings.recipes.nothingFound", 2214 "id": "settings.recipes.nothingFound",
1963 "start": { 2215 "start": {
1964 "column": 18, 2216 "column": 18,
1965 "line": 26 2217 "line": 27
1966 } 2218 }
1967 }, 2219 },
1968 { 2220 {
1969 "defaultMessage": "!!!Discover services", 2221 "defaultMessage": "!!!Discover services",
1970 "end": { 2222 "end": {
1971 "column": 3, 2223 "column": 3,
1972 "line": 33 2224 "line": 34
1973 }, 2225 },
1974 "file": "src/components/settings/services/ServicesDashboard.js", 2226 "file": "src/components/settings/services/ServicesDashboard.js",
1975 "id": "settings.services.discoverServices", 2227 "id": "settings.services.discoverServices",
1976 "start": { 2228 "start": {
1977 "column": 20, 2229 "column": 20,
1978 "line": 30 2230 "line": 31
1979 } 2231 }
1980 }, 2232 },
1981 { 2233 {
1982 "defaultMessage": "!!!Could not load your services", 2234 "defaultMessage": "!!!Could not load your services",
1983 "end": { 2235 "end": {
1984 "column": 3, 2236 "column": 3,
1985 "line": 37 2237 "line": 38
1986 }, 2238 },
1987 "file": "src/components/settings/services/ServicesDashboard.js", 2239 "file": "src/components/settings/services/ServicesDashboard.js",
1988 "id": "settings.services.servicesRequestFailed", 2240 "id": "settings.services.servicesRequestFailed",
1989 "start": { 2241 "start": {
1990 "column": 25, 2242 "column": 25,
1991 "line": 34 2243 "line": 35
1992 } 2244 }
1993 }, 2245 },
1994 { 2246 {
1995 "defaultMessage": "!!!Try again", 2247 "defaultMessage": "!!!Try again",
1996 "end": { 2248 "end": {
1997 "column": 3, 2249 "column": 3,
1998 "line": 41 2250 "line": 42
1999 }, 2251 },
2000 "file": "src/components/settings/services/ServicesDashboard.js", 2252 "file": "src/components/settings/services/ServicesDashboard.js",
2001 "id": "settings.account.tryReloadServices", 2253 "id": "settings.account.tryReloadServices",
2002 "start": { 2254 "start": {
2003 "column": 21, 2255 "column": 21,
2004 "line": 38 2256 "line": 39
2005 } 2257 }
2006 }, 2258 },
2007 { 2259 {
2008 "defaultMessage": "!!!Your changes have been saved", 2260 "defaultMessage": "!!!Your changes have been saved",
2009 "end": { 2261 "end": {
2010 "column": 3, 2262 "column": 3,
2011 "line": 45 2263 "line": 46
2012 }, 2264 },
2013 "file": "src/components/settings/services/ServicesDashboard.js", 2265 "file": "src/components/settings/services/ServicesDashboard.js",
2014 "id": "settings.services.updatedInfo", 2266 "id": "settings.services.updatedInfo",
2015 "start": { 2267 "start": {
2016 "column": 15, 2268 "column": 15,
2017 "line": 42 2269 "line": 43
2018 } 2270 }
2019 }, 2271 },
2020 { 2272 {
2021 "defaultMessage": "!!!Service has been deleted", 2273 "defaultMessage": "!!!Service has been deleted",
2022 "end": { 2274 "end": {
2023 "column": 3, 2275 "column": 3,
2024 "line": 49 2276 "line": 50
2025 }, 2277 },
2026 "file": "src/components/settings/services/ServicesDashboard.js", 2278 "file": "src/components/settings/services/ServicesDashboard.js",
2027 "id": "settings.services.deletedInfo", 2279 "id": "settings.services.deletedInfo",
2028 "start": { 2280 "start": {
2029 "column": 15, 2281 "column": 15,
2030 "line": 46 2282 "line": 47
2031 } 2283 }
2032 } 2284 }
2033 ], 2285 ],
@@ -2441,220 +2693,396 @@
2441 { 2693 {
2442 "descriptors": [ 2694 "descriptors": [
2443 { 2695 {
2444 "defaultMessage": "!!!Support the development of Franz", 2696 "defaultMessage": "!!!Choose your plan",
2697 "end": {
2698 "column": 3,
2699 "line": 16
2700 },
2701 "file": "src/components/subscription/SubscriptionForm.js",
2702 "id": "subscription.cta.choosePlan",
2703 "start": {
2704 "column": 21,
2705 "line": 13
2706 }
2707 },
2708 {
2709 "defaultMessage": "!!!Upgrade your account and get the full Franz experience",
2445 "end": { 2710 "end": {
2446 "column": 3, 2711 "column": 3,
2712 "line": 20
2713 },
2714 "file": "src/components/subscription/SubscriptionForm.js",
2715 "id": "settings.account.headlineUpgradeAccount",
2716 "start": {
2717 "column": 18,
2447 "line": 17 2718 "line": 17
2719 }
2720 },
2721 {
2722 "defaultMessage": "!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
2723 "end": {
2724 "column": 3,
2725 "line": 24
2726 },
2727 "file": "src/components/subscription/SubscriptionForm.js",
2728 "id": "subscription.teaser.intro",
2729 "start": {
2730 "column": 14,
2731 "line": 21
2732 }
2733 },
2734 {
2735 "defaultMessage": "!!!Paid Franz Plans include:",
2736 "end": {
2737 "column": 3,
2738 "line": 28
2448 }, 2739 },
2449 "file": "src/components/subscription/SubscriptionForm.js", 2740 "file": "src/components/subscription/SubscriptionForm.js",
2450 "id": "subscription.submit.label", 2741 "id": "subscription.teaser.includedFeatures",
2742 "start": {
2743 "column": 20,
2744 "line": 25
2745 }
2746 }
2747 ],
2748 "path": "src/components/subscription/SubscriptionForm.json"
2749 },
2750 {
2751 "descriptors": [
2752 {
2753 "defaultMessage": "!!!Cancel",
2754 "end": {
2755 "column": 3,
2756 "line": 14
2757 },
2758 "file": "src/components/subscription/SubscriptionPopup.js",
2759 "id": "subscriptionPopup.buttonCancel",
2760 "start": {
2761 "column": 16,
2762 "line": 11
2763 }
2764 },
2765 {
2766 "defaultMessage": "!!!Done",
2767 "end": {
2768 "column": 3,
2769 "line": 18
2770 },
2771 "file": "src/components/subscription/SubscriptionPopup.js",
2772 "id": "subscriptionPopup.buttonDone",
2773 "start": {
2774 "column": 14,
2775 "line": 15
2776 }
2777 }
2778 ],
2779 "path": "src/components/subscription/SubscriptionPopup.json"
2780 },
2781 {
2782 "descriptors": [
2783 {
2784 "defaultMessage": "!!!Yes, start the free Franz Professional trial",
2785 "end": {
2786 "column": 3,
2787 "line": 17
2788 },
2789 "file": "src/components/subscription/TrialForm.js",
2790 "id": "subscription.cta.activateTrial",
2451 "start": { 2791 "start": {
2452 "column": 21, 2792 "column": 21,
2453 "line": 14 2793 "line": 14
2454 } 2794 }
2455 }, 2795 },
2456 { 2796 {
2457 "defaultMessage": "!!!Could not initialize payment form", 2797 "defaultMessage": "!!!See all options",
2458 "end": { 2798 "end": {
2459 "column": 3, 2799 "column": 3,
2460 "line": 21 2800 "line": 21
2461 }, 2801 },
2462 "file": "src/components/subscription/SubscriptionForm.js", 2802 "file": "src/components/subscription/TrialForm.js",
2463 "id": "subscription.paymentSessionError", 2803 "id": "subscription.cta.allOptions",
2464 "start": { 2804 "start": {
2465 "column": 23, 2805 "column": 20,
2466 "line": 18 2806 "line": 18
2467 } 2807 }
2468 }, 2808 },
2469 { 2809 {
2470 "defaultMessage": "!!!free", 2810 "defaultMessage": "!!!Get the free 14 day Franz Professional Trial",
2471 "end": { 2811 "end": {
2472 "column": 3, 2812 "column": 3,
2473 "line": 25 2813 "line": 25
2474 }, 2814 },
2475 "file": "src/components/subscription/SubscriptionForm.js", 2815 "file": "src/components/subscription/TrialForm.js",
2476 "id": "subscription.type.free", 2816 "id": "settings.account.headlineTrialUpgrade",
2477 "start": { 2817 "start": {
2478 "column": 12, 2818 "column": 18,
2479 "line": 22 2819 "line": 22
2480 } 2820 }
2481 }, 2821 },
2482 { 2822 {
2483 "defaultMessage": "!!!month", 2823 "defaultMessage": "!!!The Franz Professional Plan includes:",
2484 "end": { 2824 "end": {
2485 "column": 3, 2825 "column": 3,
2486 "line": 29 2826 "line": 29
2487 }, 2827 },
2488 "file": "src/components/subscription/SubscriptionForm.js", 2828 "file": "src/components/subscription/TrialForm.js",
2489 "id": "subscription.type.month", 2829 "id": "subscription.includedProFeatures",
2490 "start": { 2830 "start": {
2491 "column": 15, 2831 "column": 20,
2492 "line": 26 2832 "line": 26
2493 } 2833 }
2494 }, 2834 },
2495 { 2835 {
2496 "defaultMessage": "!!!year", 2836 "defaultMessage": "!!!No strings attached",
2497 "end": { 2837 "end": {
2498 "column": 3, 2838 "column": 3,
2499 "line": 33 2839 "line": 33
2500 }, 2840 },
2501 "file": "src/components/subscription/SubscriptionForm.js", 2841 "file": "src/components/subscription/TrialForm.js",
2502 "id": "subscription.type.year", 2842 "id": "pricing.trial.terms.headline",
2503 "start": { 2843 "start": {
2504 "column": 14, 2844 "column": 29,
2505 "line": 30 2845 "line": 30
2506 } 2846 }
2507 }, 2847 },
2508 { 2848 {
2509 "defaultMessage": "!!!The Franz Premium Supporter Account includes", 2849 "defaultMessage": "!!!No credit card required",
2510 "end": { 2850 "end": {
2511 "column": 3, 2851 "column": 3,
2512 "line": 37 2852 "line": 37
2513 }, 2853 },
2514 "file": "src/components/subscription/SubscriptionForm.js", 2854 "file": "src/components/subscription/TrialForm.js",
2515 "id": "subscription.includedFeatures", 2855 "id": "pricing.trial.terms.noCreditCard",
2516 "start": { 2856 "start": {
2517 "column": 20, 2857 "column": 16,
2518 "line": 34 2858 "line": 34
2519 } 2859 }
2520 }, 2860 },
2521 { 2861 {
2522 "defaultMessage": "!!!Add on-premise/hosted services like Mattermost", 2862 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
2523 "end": { 2863 "end": {
2524 "column": 3, 2864 "column": 3,
2525 "line": 41 2865 "line": 41
2526 }, 2866 },
2527 "file": "src/components/subscription/SubscriptionForm.js", 2867 "file": "src/components/subscription/TrialForm.js",
2528 "id": "subscription.features.onpremise.mattermost", 2868 "id": "pricing.trial.terms.automaticTrialEnd",
2529 "start": { 2869 "start": {
2530 "column": 13, 2870 "column": 21,
2531 "line": 38 2871 "line": 38
2532 } 2872 }
2873 }
2874 ],
2875 "path": "src/components/subscription/TrialForm.json"
2876 },
2877 {
2878 "descriptors": [
2879 {
2880 "defaultMessage": "!!!Your trial was successfully activated. Happy messaging!",
2881 "end": {
2882 "column": 3,
2883 "line": 14
2884 },
2885 "file": "src/components/TrialActivationInfoBar.js",
2886 "id": "infobar.trialActivated",
2887 "start": {
2888 "column": 11,
2889 "line": 11
2890 }
2891 }
2892 ],
2893 "path": "src/components/TrialActivationInfoBar.json"
2894 },
2895 {
2896 "descriptors": [
2897 {
2898 "defaultMessage": "!!!Get a Franz Supporter License",
2899 "end": {
2900 "column": 3,
2901 "line": 16
2902 },
2903 "file": "src/components/ui/ActivateTrialButton/index.js",
2904 "id": "feature.delayApp.upgrade.action",
2905 "start": {
2906 "column": 10,
2907 "line": 13
2908 }
2533 }, 2909 },
2534 { 2910 {
2535 "defaultMessage": "!!!No app delays & nagging to upgrade license", 2911 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
2536 "end": { 2912 "end": {
2537 "column": 3, 2913 "column": 3,
2538 "line": 45 2914 "line": 20
2539 }, 2915 },
2540 "file": "src/components/subscription/SubscriptionForm.js", 2916 "file": "src/components/ui/ActivateTrialButton/index.js",
2541 "id": "subscription.features.noInterruptions", 2917 "id": "feature.delayApp.trial.action",
2542 "start": { 2918 "start": {
2543 "column": 19, 2919 "column": 15,
2544 "line": 42 2920 "line": 17
2545 } 2921 }
2546 }, 2922 },
2547 { 2923 {
2548 "defaultMessage": "!!!Proxy support for services", 2924 "defaultMessage": "!!!Upgrade account",
2549 "end": { 2925 "end": {
2550 "column": 3, 2926 "column": 3,
2551 "line": 49 2927 "line": 24
2552 }, 2928 },
2553 "file": "src/components/subscription/SubscriptionForm.js", 2929 "file": "src/components/ui/ActivateTrialButton/index.js",
2554 "id": "subscription.features.proxy", 2930 "id": "feature.delayApp.upgrade.actionShort",
2555 "start": { 2931 "start": {
2556 "column": 9, 2932 "column": 15,
2557 "line": 46 2933 "line": 21
2558 } 2934 }
2559 }, 2935 },
2560 { 2936 {
2561 "defaultMessage": "!!!Support for Spellchecker", 2937 "defaultMessage": "!!!Activate the free Franz Professional trial",
2562 "end": { 2938 "end": {
2563 "column": 3, 2939 "column": 3,
2564 "line": 53 2940 "line": 28
2565 }, 2941 },
2566 "file": "src/components/subscription/SubscriptionForm.js", 2942 "file": "src/components/ui/ActivateTrialButton/index.js",
2567 "id": "subscription.features.spellchecker", 2943 "id": "feature.delayApp.trial.actionShort",
2944 "start": {
2945 "column": 20,
2946 "line": 25
2947 }
2948 }
2949 ],
2950 "path": "src/components/ui/ActivateTrialButton/index.json"
2951 },
2952 {
2953 "descriptors": [
2954 {
2955 "defaultMessage": "!!!Add unlimited services",
2956 "end": {
2957 "column": 3,
2958 "line": 11
2959 },
2960 "file": "src/components/ui/FeatureList.js",
2961 "id": "pricing.features.unlimitedServices",
2962 "start": {
2963 "column": 21,
2964 "line": 8
2965 }
2966 },
2967 {
2968 "defaultMessage": "!!!Spellchecker support",
2969 "end": {
2970 "column": 3,
2971 "line": 15
2972 },
2973 "file": "src/components/ui/FeatureList.js",
2974 "id": "pricing.features.spellchecker",
2568 "start": { 2975 "start": {
2569 "column": 16, 2976 "column": 16,
2570 "line": 50 2977 "line": 12
2571 } 2978 }
2572 }, 2979 },
2573 { 2980 {
2574 "defaultMessage": "!!!Organize your services in workspaces", 2981 "defaultMessage": "!!!Workspaces",
2575 "end": { 2982 "end": {
2576 "column": 3, 2983 "column": 3,
2577 "line": 57 2984 "line": 19
2578 }, 2985 },
2579 "file": "src/components/subscription/SubscriptionForm.js", 2986 "file": "src/components/ui/FeatureList.js",
2580 "id": "subscription.features.workspaces", 2987 "id": "pricing.features.workspaces",
2581 "start": { 2988 "start": {
2582 "column": 14, 2989 "column": 14,
2583 "line": 54 2990 "line": 16
2584 } 2991 }
2585 }, 2992 },
2586 { 2993 {
2587 "defaultMessage": "!!!No ads, ever!", 2994 "defaultMessage": "!!!Add Custom Websites",
2588 "end": { 2995 "end": {
2589 "column": 3, 2996 "column": 3,
2590 "line": 61 2997 "line": 23
2591 }, 2998 },
2592 "file": "src/components/subscription/SubscriptionForm.js", 2999 "file": "src/components/ui/FeatureList.js",
2593 "id": "subscription.features.ads", 3000 "id": "pricing.features.customWebsites",
2594 "start": { 3001 "start": {
2595 "column": 7, 3002 "column": 18,
2596 "line": 58 3003 "line": 20
2597 } 3004 }
2598 }, 3005 },
2599 { 3006 {
2600 "defaultMessage": "!!!coming soon", 3007 "defaultMessage": "!!!On-premise & other Hosted Services",
2601 "end": { 3008 "end": {
2602 "column": 3, 3009 "column": 3,
2603 "line": 65 3010 "line": 27
2604 }, 3011 },
2605 "file": "src/components/subscription/SubscriptionForm.js", 3012 "file": "src/components/ui/FeatureList.js",
2606 "id": "subscription.features.comingSoon", 3013 "id": "pricing.features.onPremise",
2607 "start": { 3014 "start": {
2608 "column": 14, 3015 "column": 13,
2609 "line": 62 3016 "line": 24
2610 } 3017 }
2611 }, 3018 },
2612 { 3019 {
2613 "defaultMessage": "!!!EU residents: local sales tax may apply", 3020 "defaultMessage": "!!!Install 3rd party services",
2614 "end": { 3021 "end": {
2615 "column": 3, 3022 "column": 3,
2616 "line": 69 3023 "line": 31
2617 }, 3024 },
2618 "file": "src/components/subscription/SubscriptionForm.js", 3025 "file": "src/components/ui/FeatureList.js",
2619 "id": "subscription.euTaxInfo", 3026 "id": "pricing.features.thirdPartyServices",
2620 "start": { 3027 "start": {
2621 "column": 13, 3028 "column": 22,
2622 "line": 66 3029 "line": 28
2623 } 3030 }
2624 } 3031 },
2625 ],
2626 "path": "src/components/subscription/SubscriptionForm.json"
2627 },
2628 {
2629 "descriptors": [
2630 { 3032 {
2631 "defaultMessage": "!!!Cancel", 3033 "defaultMessage": "!!!Service Proxies",
2632 "end": { 3034 "end": {
2633 "column": 3, 3035 "column": 3,
2634 "line": 14 3036 "line": 35
2635 }, 3037 },
2636 "file": "src/components/subscription/SubscriptionPopup.js", 3038 "file": "src/components/ui/FeatureList.js",
2637 "id": "subscriptionPopup.buttonCancel", 3039 "id": "pricing.features.serviceProxies",
2638 "start": { 3040 "start": {
2639 "column": 16, 3041 "column": 18,
2640 "line": 11 3042 "line": 32
2641 } 3043 }
2642 }, 3044 },
2643 { 3045 {
2644 "defaultMessage": "!!!Done", 3046 "defaultMessage": "!!!Team Management",
2645 "end": { 3047 "end": {
2646 "column": 3, 3048 "column": 3,
2647 "line": 18 3049 "line": 39
2648 }, 3050 },
2649 "file": "src/components/subscription/SubscriptionPopup.js", 3051 "file": "src/components/ui/FeatureList.js",
2650 "id": "subscriptionPopup.buttonDone", 3052 "id": "pricing.features.teamManagement",
2651 "start": { 3053 "start": {
2652 "column": 14, 3054 "column": 18,
2653 "line": 15 3055 "line": 36
3056 }
3057 },
3058 {
3059 "defaultMessage": "!!!No Waiting Screens",
3060 "end": {
3061 "column": 3,
3062 "line": 43
3063 },
3064 "file": "src/components/ui/FeatureList.js",
3065 "id": "pricing.features.appDelays",
3066 "start": {
3067 "column": 13,
3068 "line": 40
3069 }
3070 },
3071 {
3072 "defaultMessage": "!!!Forever ad-free",
3073 "end": {
3074 "column": 3,
3075 "line": 47
3076 },
3077 "file": "src/components/ui/FeatureList.js",
3078 "id": "pricing.features.adFree",
3079 "start": {
3080 "column": 10,
3081 "line": 44
2654 } 3082 }
2655 } 3083 }
2656 ], 3084 ],
2657 "path": "src/components/subscription/SubscriptionPopup.json" 3085 "path": "src/components/ui/FeatureList.json"
2658 }, 3086 },
2659 { 3087 {
2660 "descriptors": [ 3088 "descriptors": [
@@ -2662,13 +3090,13 @@
2662 "defaultMessage": "!!!Upgrade account", 3090 "defaultMessage": "!!!Upgrade account",
2663 "end": { 3091 "end": {
2664 "column": 3, 3092 "column": 3,
2665 "line": 18 3093 "line": 19
2666 }, 3094 },
2667 "file": "src/components/ui/PremiumFeatureContainer/index.js", 3095 "file": "src/components/ui/PremiumFeatureContainer/index.js",
2668 "id": "premiumFeature.button.upgradeAccount", 3096 "id": "premiumFeature.button.upgradeAccount",
2669 "start": { 3097 "start": {
2670 "column": 10, 3098 "column": 10,
2671 "line": 15 3099 "line": 16
2672 } 3100 }
2673 } 3101 }
2674 ], 3102 ],
@@ -3230,39 +3658,65 @@
3230 "defaultMessage": "!!!Please purchase license to skip waiting", 3658 "defaultMessage": "!!!Please purchase license to skip waiting",
3231 "end": { 3659 "end": {
3232 "column": 3, 3660 "column": 3,
3233 "line": 18 3661 "line": 20
3234 }, 3662 },
3235 "file": "src/features/delayApp/Component.js", 3663 "file": "src/features/delayApp/Component.js",
3236 "id": "feature.delayApp.headline", 3664 "id": "feature.delayApp.headline",
3237 "start": { 3665 "start": {
3238 "column": 12, 3666 "column": 12,
3239 "line": 15 3667 "line": 17
3668 }
3669 },
3670 {
3671 "defaultMessage": "!!!Get the free Franz Professional 14 day trial and skip the line",
3672 "end": {
3673 "column": 3,
3674 "line": 24
3675 },
3676 "file": "src/features/delayApp/Component.js",
3677 "id": "feature.delayApp.trial.headline",
3678 "start": {
3679 "column": 17,
3680 "line": 21
3240 } 3681 }
3241 }, 3682 },
3242 { 3683 {
3243 "defaultMessage": "!!!Get a Franz Supporter License", 3684 "defaultMessage": "!!!Get a Franz Supporter License",
3244 "end": { 3685 "end": {
3245 "column": 3, 3686 "column": 3,
3246 "line": 22 3687 "line": 28
3247 }, 3688 },
3248 "file": "src/features/delayApp/Component.js", 3689 "file": "src/features/delayApp/Component.js",
3249 "id": "feature.delayApp.action", 3690 "id": "feature.delayApp.upgrade.action",
3250 "start": { 3691 "start": {
3251 "column": 10, 3692 "column": 10,
3252 "line": 19 3693 "line": 25
3694 }
3695 },
3696 {
3697 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
3698 "end": {
3699 "column": 3,
3700 "line": 32
3701 },
3702 "file": "src/features/delayApp/Component.js",
3703 "id": "feature.delayApp.trial.action",
3704 "start": {
3705 "column": 15,
3706 "line": 29
3253 } 3707 }
3254 }, 3708 },
3255 { 3709 {
3256 "defaultMessage": "!!!Franz will continue in {seconds} seconds.", 3710 "defaultMessage": "!!!Franz will continue in {seconds} seconds.",
3257 "end": { 3711 "end": {
3258 "column": 3, 3712 "column": 3,
3259 "line": 26 3713 "line": 36
3260 }, 3714 },
3261 "file": "src/features/delayApp/Component.js", 3715 "file": "src/features/delayApp/Component.js",
3262 "id": "feature.delayApp.text", 3716 "id": "feature.delayApp.text",
3263 "start": { 3717 "start": {
3264 "column": 8, 3718 "column": 8,
3265 "line": 23 3719 "line": 33
3266 } 3720 }
3267 } 3721 }
3268 ], 3722 ],
@@ -3271,94 +3725,143 @@
3271 { 3725 {
3272 "descriptors": [ 3726 "descriptors": [
3273 { 3727 {
3274 "defaultMessage": "!!!Franz is better together!", 3728 "defaultMessage": "!!!Changes in Franz {version}",
3729 "end": {
3730 "column": 3,
3731 "line": 23
3732 },
3733 "file": "src/features/serviceLimit/components/AnnouncementScreen.js",
3734 "id": "feature.announcements.changelog.headline",
3735 "start": {
3736 "column": 12,
3737 "line": 20
3738 }
3739 }
3740 ],
3741 "path": "src/features/serviceLimit/components/AnnouncementScreen.json"
3742 },
3743 {
3744 "descriptors": [
3745 {
3746 "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.",
3747 "end": {
3748 "column": 3,
3749 "line": 14
3750 },
3751 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3752 "id": "feature.serviceLimit.limitReached",
3753 "start": {
3754 "column": 16,
3755 "line": 11
3756 }
3757 },
3758 {
3759 "defaultMessage": "!!!Upgrade account",
3275 "end": { 3760 "end": {
3276 "column": 3, 3761 "column": 3,
3277 "line": 18 3762 "line": 18
3278 }, 3763 },
3764 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
3765 "id": "premiumFeature.button.upgradeAccount",
3766 "start": {
3767 "column": 10,
3768 "line": 15
3769 }
3770 }
3771 ],
3772 "path": "src/features/serviceLimit/components/LimitReachedInfobox.json"
3773 },
3774 {
3775 "descriptors": [
3776 {
3777 "defaultMessage": "!!!Franz is better together!",
3778 "end": {
3779 "column": 3,
3780 "line": 21
3781 },
3279 "file": "src/features/shareFranz/Component.js", 3782 "file": "src/features/shareFranz/Component.js",
3280 "id": "feature.shareFranz.headline", 3783 "id": "feature.shareFranz.headline",
3281 "start": { 3784 "start": {
3282 "column": 12, 3785 "column": 12,
3283 "line": 15 3786 "line": 18
3284 } 3787 }
3285 }, 3788 },
3286 { 3789 {
3287 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.", 3790 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
3288 "end": { 3791 "end": {
3289 "column": 3, 3792 "column": 3,
3290 "line": 22 3793 "line": 25
3291 }, 3794 },
3292 "file": "src/features/shareFranz/Component.js", 3795 "file": "src/features/shareFranz/Component.js",
3293 "id": "feature.shareFranz.text", 3796 "id": "feature.shareFranz.text",
3294 "start": { 3797 "start": {
3295 "column": 8, 3798 "column": 8,
3296 "line": 19 3799 "line": 22
3297 } 3800 }
3298 }, 3801 },
3299 { 3802 {
3300 "defaultMessage": "!!!Share as email", 3803 "defaultMessage": "!!!Share as email",
3301 "end": { 3804 "end": {
3302 "column": 3, 3805 "column": 3,
3303 "line": 26 3806 "line": 29
3304 }, 3807 },
3305 "file": "src/features/shareFranz/Component.js", 3808 "file": "src/features/shareFranz/Component.js",
3306 "id": "feature.shareFranz.action.email", 3809 "id": "feature.shareFranz.action.email",
3307 "start": { 3810 "start": {
3308 "column": 16, 3811 "column": 16,
3309 "line": 23 3812 "line": 26
3310 } 3813 }
3311 }, 3814 },
3312 { 3815 {
3313 "defaultMessage": "!!!Share on Facebook", 3816 "defaultMessage": "!!!Share on Facebook",
3314 "end": { 3817 "end": {
3315 "column": 3, 3818 "column": 3,
3316 "line": 30 3819 "line": 33
3317 }, 3820 },
3318 "file": "src/features/shareFranz/Component.js", 3821 "file": "src/features/shareFranz/Component.js",
3319 "id": "feature.shareFranz.action.facebook", 3822 "id": "feature.shareFranz.action.facebook",
3320 "start": { 3823 "start": {
3321 "column": 19, 3824 "column": 19,
3322 "line": 27 3825 "line": 30
3323 } 3826 }
3324 }, 3827 },
3325 { 3828 {
3326 "defaultMessage": "!!!Share on Twitter", 3829 "defaultMessage": "!!!Share on Twitter",
3327 "end": { 3830 "end": {
3328 "column": 3, 3831 "column": 3,
3329 "line": 34 3832 "line": 37
3330 }, 3833 },
3331 "file": "src/features/shareFranz/Component.js", 3834 "file": "src/features/shareFranz/Component.js",
3332 "id": "feature.shareFranz.action.twitter", 3835 "id": "feature.shareFranz.action.twitter",
3333 "start": { 3836 "start": {
3334 "column": 18, 3837 "column": 18,
3335 "line": 31 3838 "line": 34
3336 } 3839 }
3337 }, 3840 },
3338 { 3841 {
3339 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", 3842 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
3340 "end": { 3843 "end": {
3341 "column": 3, 3844 "column": 3,
3342 "line": 38 3845 "line": 41
3343 }, 3846 },
3344 "file": "src/features/shareFranz/Component.js", 3847 "file": "src/features/shareFranz/Component.js",
3345 "id": "feature.shareFranz.shareText.email", 3848 "id": "feature.shareFranz.shareText.email",
3346 "start": { 3849 "start": {
3347 "column": 18, 3850 "column": 18,
3348 "line": 35 3851 "line": 38
3349 } 3852 }
3350 }, 3853 },
3351 { 3854 {
3352 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger", 3855 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
3353 "end": { 3856 "end": {
3354 "column": 3, 3857 "column": 3,
3355 "line": 42 3858 "line": 45
3356 }, 3859 },
3357 "file": "src/features/shareFranz/Component.js", 3860 "file": "src/features/shareFranz/Component.js",
3358 "id": "feature.shareFranz.shareText.twitter", 3861 "id": "feature.shareFranz.shareText.twitter",
3359 "start": { 3862 "start": {
3360 "column": 20, 3863 "column": 20,
3361 "line": 39 3864 "line": 42
3362 } 3865 }
3363 } 3866 }
3364 ], 3867 ],
@@ -3367,6 +3870,50 @@
3367 { 3870 {
3368 "descriptors": [ 3871 "descriptors": [
3369 { 3872 {
3873 "defaultMessage": "!!!The Franz Todos Preview is currently only available for Franz Premium accounts.",
3874 "end": {
3875 "column": 3,
3876 "line": 22
3877 },
3878 "file": "src/features/todos/components/TodosWebview.js",
3879 "id": "feature.todos.premium.info",
3880 "start": {
3881 "column": 15,
3882 "line": 19
3883 }
3884 },
3885 {
3886 "defaultMessage": "!!!Upgrade Account",
3887 "end": {
3888 "column": 3,
3889 "line": 26
3890 },
3891 "file": "src/features/todos/components/TodosWebview.js",
3892 "id": "feature.todos.premium.upgrade",
3893 "start": {
3894 "column": 14,
3895 "line": 23
3896 }
3897 },
3898 {
3899 "defaultMessage": "!!!Franz Todos will be available to everyone soon.",
3900 "end": {
3901 "column": 3,
3902 "line": 30
3903 },
3904 "file": "src/features/todos/components/TodosWebview.js",
3905 "id": "feature.todos.premium.rollout",
3906 "start": {
3907 "column": 15,
3908 "line": 27
3909 }
3910 }
3911 ],
3912 "path": "src/features/todos/components/TodosWebview.json"
3913 },
3914 {
3915 "descriptors": [
3916 {
3370 "defaultMessage": "!!!Create workspace", 3917 "defaultMessage": "!!!Create workspace",
3371 "end": { 3918 "end": {
3372 "column": 3, 3919 "column": 3,
@@ -3497,104 +4044,104 @@
3497 "defaultMessage": "!!!Workspaces", 4044 "defaultMessage": "!!!Workspaces",
3498 "end": { 4045 "end": {
3499 "column": 3, 4046 "column": 3,
3500 "line": 19 4047 "line": 20
3501 }, 4048 },
3502 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4049 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3503 "id": "workspaceDrawer.headline", 4050 "id": "workspaceDrawer.headline",
3504 "start": { 4051 "start": {
3505 "column": 12, 4052 "column": 12,
3506 "line": 16 4053 "line": 17
3507 } 4054 }
3508 }, 4055 },
3509 { 4056 {
3510 "defaultMessage": "!!!All services", 4057 "defaultMessage": "!!!All services",
3511 "end": { 4058 "end": {
3512 "column": 3, 4059 "column": 3,
3513 "line": 23 4060 "line": 24
3514 }, 4061 },
3515 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4062 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3516 "id": "workspaceDrawer.allServices", 4063 "id": "workspaceDrawer.allServices",
3517 "start": { 4064 "start": {
3518 "column": 15, 4065 "column": 15,
3519 "line": 20 4066 "line": 21
3520 } 4067 }
3521 }, 4068 },
3522 { 4069 {
3523 "defaultMessage": "!!!Workspaces settings", 4070 "defaultMessage": "!!!Workspaces settings",
3524 "end": { 4071 "end": {
3525 "column": 3, 4072 "column": 3,
3526 "line": 27 4073 "line": 28
3527 }, 4074 },
3528 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4075 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3529 "id": "workspaceDrawer.workspacesSettingsTooltip", 4076 "id": "workspaceDrawer.workspacesSettingsTooltip",
3530 "start": { 4077 "start": {
3531 "column": 29, 4078 "column": 29,
3532 "line": 24 4079 "line": 25
3533 } 4080 }
3534 }, 4081 },
3535 { 4082 {
3536 "defaultMessage": "!!!Info about workspace feature", 4083 "defaultMessage": "!!!Info about workspace feature",
3537 "end": { 4084 "end": {
3538 "column": 3, 4085 "column": 3,
3539 "line": 31 4086 "line": 32
3540 }, 4087 },
3541 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4088 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3542 "id": "workspaceDrawer.workspaceFeatureInfo", 4089 "id": "workspaceDrawer.workspaceFeatureInfo",
3543 "start": { 4090 "start": {
3544 "column": 24, 4091 "column": 24,
3545 "line": 28 4092 "line": 29
3546 } 4093 }
3547 }, 4094 },
3548 { 4095 {
3549 "defaultMessage": "!!!Create your first workspace", 4096 "defaultMessage": "!!!Create your first workspace",
3550 "end": { 4097 "end": {
3551 "column": 3, 4098 "column": 3,
3552 "line": 35 4099 "line": 36
3553 }, 4100 },
3554 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4101 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3555 "id": "workspaceDrawer.premiumCtaButtonLabel", 4102 "id": "workspaceDrawer.premiumCtaButtonLabel",
3556 "start": { 4103 "start": {
3557 "column": 25, 4104 "column": 25,
3558 "line": 32 4105 "line": 33
3559 } 4106 }
3560 }, 4107 },
3561 { 4108 {
3562 "defaultMessage": "!!!Reactivate premium account", 4109 "defaultMessage": "!!!Reactivate premium account",
3563 "end": { 4110 "end": {
3564 "column": 3, 4111 "column": 3,
3565 "line": 39 4112 "line": 40
3566 }, 4113 },
3567 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4114 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3568 "id": "workspaceDrawer.reactivatePremiumAccountLabel", 4115 "id": "workspaceDrawer.reactivatePremiumAccountLabel",
3569 "start": { 4116 "start": {
3570 "column": 28, 4117 "column": 28,
3571 "line": 36 4118 "line": 37
3572 } 4119 }
3573 }, 4120 },
3574 { 4121 {
3575 "defaultMessage": "!!!add new workspace", 4122 "defaultMessage": "!!!add new workspace",
3576 "end": { 4123 "end": {
3577 "column": 3, 4124 "column": 3,
3578 "line": 43 4125 "line": 44
3579 }, 4126 },
3580 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4127 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3581 "id": "workspaceDrawer.addNewWorkspaceLabel", 4128 "id": "workspaceDrawer.addNewWorkspaceLabel",
3582 "start": { 4129 "start": {
3583 "column": 24, 4130 "column": 24,
3584 "line": 40 4131 "line": 41
3585 } 4132 }
3586 }, 4133 },
3587 { 4134 {
3588 "defaultMessage": "!!!Premium feature", 4135 "defaultMessage": "!!!Premium feature",
3589 "end": { 4136 "end": {
3590 "column": 3, 4137 "column": 3,
3591 "line": 47 4138 "line": 48
3592 }, 4139 },
3593 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 4140 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
3594 "id": "workspaceDrawer.proFeatureBadge", 4141 "id": "workspaceDrawer.proFeatureBadge",
3595 "start": { 4142 "start": {
3596 "column": 23, 4143 "column": 23,
3597 "line": 44 4144 "line": 45
3598 } 4145 }
3599 } 4146 }
3600 ], 4147 ],
@@ -3637,104 +4184,104 @@
3637 "defaultMessage": "!!!Your workspaces", 4184 "defaultMessage": "!!!Your workspaces",
3638 "end": { 4185 "end": {
3639 "column": 3, 4186 "column": 3,
3640 "line": 20 4187 "line": 22
3641 }, 4188 },
3642 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4189 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3643 "id": "settings.workspaces.headline", 4190 "id": "settings.workspaces.headline",
3644 "start": { 4191 "start": {
3645 "column": 12, 4192 "column": 12,
3646 "line": 17 4193 "line": 19
3647 } 4194 }
3648 }, 4195 },
3649 { 4196 {
3650 "defaultMessage": "!!!You haven't added any workspaces yet.", 4197 "defaultMessage": "!!!You haven't added any workspaces yet.",
3651 "end": { 4198 "end": {
3652 "column": 3, 4199 "column": 3,
3653 "line": 24 4200 "line": 26
3654 }, 4201 },
3655 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4202 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3656 "id": "settings.workspaces.noWorkspacesAdded", 4203 "id": "settings.workspaces.noWorkspacesAdded",
3657 "start": { 4204 "start": {
3658 "column": 19, 4205 "column": 19,
3659 "line": 21 4206 "line": 23
3660 } 4207 }
3661 }, 4208 },
3662 { 4209 {
3663 "defaultMessage": "!!!Could not load your workspaces", 4210 "defaultMessage": "!!!Could not load your workspaces",
3664 "end": { 4211 "end": {
3665 "column": 3, 4212 "column": 3,
3666 "line": 28 4213 "line": 30
3667 }, 4214 },
3668 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4215 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3669 "id": "settings.workspaces.workspacesRequestFailed", 4216 "id": "settings.workspaces.workspacesRequestFailed",
3670 "start": { 4217 "start": {
3671 "column": 27, 4218 "column": 27,
3672 "line": 25 4219 "line": 27
3673 } 4220 }
3674 }, 4221 },
3675 { 4222 {
3676 "defaultMessage": "!!!Try again", 4223 "defaultMessage": "!!!Try again",
3677 "end": { 4224 "end": {
3678 "column": 3, 4225 "column": 3,
3679 "line": 32 4226 "line": 34
3680 }, 4227 },
3681 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4228 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3682 "id": "settings.workspaces.tryReloadWorkspaces", 4229 "id": "settings.workspaces.tryReloadWorkspaces",
3683 "start": { 4230 "start": {
3684 "column": 23, 4231 "column": 23,
3685 "line": 29 4232 "line": 31
3686 } 4233 }
3687 }, 4234 },
3688 { 4235 {
3689 "defaultMessage": "!!!Your changes have been saved", 4236 "defaultMessage": "!!!Your changes have been saved",
3690 "end": { 4237 "end": {
3691 "column": 3, 4238 "column": 3,
3692 "line": 36 4239 "line": 38
3693 }, 4240 },
3694 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4241 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3695 "id": "settings.workspaces.updatedInfo", 4242 "id": "settings.workspaces.updatedInfo",
3696 "start": { 4243 "start": {
3697 "column": 15, 4244 "column": 15,
3698 "line": 33 4245 "line": 35
3699 } 4246 }
3700 }, 4247 },
3701 { 4248 {
3702 "defaultMessage": "!!!Workspace has been deleted", 4249 "defaultMessage": "!!!Workspace has been deleted",
3703 "end": { 4250 "end": {
3704 "column": 3, 4251 "column": 3,
3705 "line": 40 4252 "line": 42
3706 }, 4253 },
3707 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4254 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3708 "id": "settings.workspaces.deletedInfo", 4255 "id": "settings.workspaces.deletedInfo",
3709 "start": { 4256 "start": {
3710 "column": 15, 4257 "column": 15,
3711 "line": 37 4258 "line": 39
3712 } 4259 }
3713 }, 4260 },
3714 { 4261 {
3715 "defaultMessage": "!!!Info about workspace feature", 4262 "defaultMessage": "!!!Info about workspace feature",
3716 "end": { 4263 "end": {
3717 "column": 3, 4264 "column": 3,
3718 "line": 44 4265 "line": 46
3719 }, 4266 },
3720 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4267 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3721 "id": "settings.workspaces.workspaceFeatureInfo", 4268 "id": "settings.workspaces.workspaceFeatureInfo",
3722 "start": { 4269 "start": {
3723 "column": 24, 4270 "column": 24,
3724 "line": 41 4271 "line": 43
3725 } 4272 }
3726 }, 4273 },
3727 { 4274 {
3728 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", 4275 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
3729 "end": { 4276 "end": {
3730 "column": 3, 4277 "column": 3,
3731 "line": 48 4278 "line": 50
3732 }, 4279 },
3733 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 4280 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
3734 "id": "settings.workspaces.workspaceFeatureHeadline", 4281 "id": "settings.workspaces.workspaceFeatureHeadline",
3735 "start": { 4282 "start": {
3736 "column": 28, 4283 "column": 28,
3737 "line": 45 4284 "line": 47
3738 } 4285 }
3739 } 4286 }
3740 ], 4287 ],
@@ -3761,6 +4308,146 @@
3761 { 4308 {
3762 "descriptors": [ 4309 "descriptors": [
3763 { 4310 {
4311 "defaultMessage": "!!!Franz Professional",
4312 "end": {
4313 "column": 3,
4314 "line": 8
4315 },
4316 "file": "src/helpers/plan-helpers.js",
4317 "id": "pricing.plan.pro",
4318 "start": {
4319 "column": 15,
4320 "line": 5
4321 }
4322 },
4323 {
4324 "defaultMessage": "!!!Franz Personal",
4325 "end": {
4326 "column": 3,
4327 "line": 12
4328 },
4329 "file": "src/helpers/plan-helpers.js",
4330 "id": "pricing.plan.personal",
4331 "start": {
4332 "column": 20,
4333 "line": 9
4334 }
4335 },
4336 {
4337 "defaultMessage": "!!!Franz Free",
4338 "end": {
4339 "column": 3,
4340 "line": 16
4341 },
4342 "file": "src/helpers/plan-helpers.js",
4343 "id": "pricing.plan.free",
4344 "start": {
4345 "column": 16,
4346 "line": 13
4347 }
4348 },
4349 {
4350 "defaultMessage": "!!!Franz Premium",
4351 "end": {
4352 "column": 3,
4353 "line": 20
4354 },
4355 "file": "src/helpers/plan-helpers.js",
4356 "id": "pricing.plan.legacy",
4357 "start": {
4358 "column": 18,
4359 "line": 17
4360 }
4361 }
4362 ],
4363 "path": "src/helpers/plan-helpers.json"
4364 },
4365 {
4366 "descriptors": [
4367 {
4368 "defaultMessage": "!!!Franz Professional Yearly",
4369 "end": {
4370 "column": 3,
4371 "line": 8
4372 },
4373 "file": "src/helpers/pricing-helpers.js",
4374 "id": "pricing.plan.pro-yearly",
4375 "start": {
4376 "column": 22,
4377 "line": 5
4378 }
4379 },
4380 {
4381 "defaultMessage": "!!!Franz Professional Monthly",
4382 "end": {
4383 "column": 3,
4384 "line": 12
4385 },
4386 "file": "src/helpers/pricing-helpers.js",
4387 "id": "pricing.plan.pro-monthly",
4388 "start": {
4389 "column": 23,
4390 "line": 9
4391 }
4392 },
4393 {
4394 "defaultMessage": "!!!Franz Personal Yearly",
4395 "end": {
4396 "column": 3,
4397 "line": 16
4398 },
4399 "file": "src/helpers/pricing-helpers.js",
4400 "id": "pricing.plan.personal-yearly",
4401 "start": {
4402 "column": 27,
4403 "line": 13
4404 }
4405 },
4406 {
4407 "defaultMessage": "!!!Franz Personal Monthly",
4408 "end": {
4409 "column": 3,
4410 "line": 20
4411 },
4412 "file": "src/helpers/pricing-helpers.js",
4413 "id": "pricing.plan.personal-monthly",
4414 "start": {
4415 "column": 28,
4416 "line": 17
4417 }
4418 },
4419 {
4420 "defaultMessage": "!!!Franz Free",
4421 "end": {
4422 "column": 3,
4423 "line": 24
4424 },
4425 "file": "src/helpers/pricing-helpers.js",
4426 "id": "pricing.plan.free",
4427 "start": {
4428 "column": 16,
4429 "line": 21
4430 }
4431 },
4432 {
4433 "defaultMessage": "!!!Franz Premium",
4434 "end": {
4435 "column": 3,
4436 "line": 28
4437 },
4438 "file": "src/helpers/pricing-helpers.js",
4439 "id": "pricing.plan.legacy",
4440 "start": {
4441 "column": 18,
4442 "line": 25
4443 }
4444 }
4445 ],
4446 "path": "src/helpers/pricing-helpers.json"
4447 },
4448 {
4449 "descriptors": [
4450 {
3764 "defaultMessage": "!!!Field is required", 4451 "defaultMessage": "!!!Field is required",
3765 "end": { 4452 "end": {
3766 "column": 3, 4453 "column": 3,
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index cee42c350..f11d5ca91 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -2,9 +2,14 @@
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.actionShort": "Activate the free Franz Professional trial",
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",
11 "feature.delayApp.upgrade.actionShort": "Upgrade account",
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.",
8 "feature.shareFranz.action.email": "Send as email", 13 "feature.shareFranz.action.email": "Send as email",
9 "feature.shareFranz.action.facebook": "Share on Facebook", 14 "feature.shareFranz.action.facebook": "Share on Facebook",
10 "feature.shareFranz.action.twitter": "Share on Twitter", 15 "feature.shareFranz.action.twitter": "Share on Twitter",
@@ -12,6 +17,9 @@
12 "feature.shareFranz.shareText.email": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", 17 "feature.shareFranz.shareText.email": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
13 "feature.shareFranz.shareText.twitter": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger", 18 "feature.shareFranz.shareText.twitter": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
14 "feature.shareFranz.text": "Tell your friends and colleagues how awesome Franz is and help us to spread the word.", 19 "feature.shareFranz.text": "Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
20 "feature.todos.premium.info": "The Franz Todos Preview is currently only available for Franz Premium accounts.",
21 "feature.todos.premium.rollout": "Franz Todos will be available to everyone soon.",
22 "feature.todos.premium.upgrade": "Upgrade Account",
15 "global.api.unhealthy": "Can't connect to Franz online services", 23 "global.api.unhealthy": "Can't connect to Franz online services",
16 "global.notConnectedToTheInternet": "You are not connected to the internet.", 24 "global.notConnectedToTheInternet": "You are not connected to the internet.",
17 "global.spellchecker.useDefault": "Use System Default ({default})", 25 "global.spellchecker.useDefault": "Use System Default ({default})",
@@ -27,6 +35,7 @@
27 "infobar.buttonReloadServices": "Reload services", 35 "infobar.buttonReloadServices": "Reload services",
28 "infobar.requiredRequestsFailed": "Could not load services and user information", 36 "infobar.requiredRequestsFailed": "Could not load services and user information",
29 "infobar.servicesUpdated": "Your services have been updated.", 37 "infobar.servicesUpdated": "Your services have been updated.",
38 "infobar.trialActivated": "Your trial was successfully activated. Happy messaging!",
30 "infobar.updateAvailable": "A new update for Franz is available.", 39 "infobar.updateAvailable": "A new update for Franz is available.",
31 "invite.email.label": "Email address", 40 "invite.email.label": "Email address",
32 "invite.headline.friends": "Invite 3 of your friends or colleagues", 41 "invite.headline.friends": "Invite 3 of your friends or colleagues",
@@ -91,7 +100,7 @@
91 "menu.view.toggleDevTools": "Toggle Developer Tools", 100 "menu.view.toggleDevTools": "Toggle Developer Tools",
92 "menu.view.toggleFullScreen": "Toggle Full Screen", 101 "menu.view.toggleFullScreen": "Toggle Full Screen",
93 "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools", 102 "menu.view.toggleServiceDevTools": "Toggle Service Developer Tools",
94 "menu.view.toggleTodosDevTools": "!!!Toggle Todos Developer Tools", 103 "menu.view.toggleTodosDevTools": "Toggle Todos Developer Tools",
95 "menu.view.zoomIn": "Zoom In", 104 "menu.view.zoomIn": "Zoom In",
96 "menu.view.zoomOut": "Zoom Out", 105 "menu.view.zoomOut": "Zoom Out",
97 "menu.window": "Window", 106 "menu.window": "Window",
@@ -110,10 +119,33 @@
110 "password.submit.label": "Submit", 119 "password.submit.label": "Submit",
111 "password.successInfo": "Please check your email", 120 "password.successInfo": "Please check your email",
112 "premiumFeature.button.upgradeAccount": "Upgrade account", 121 "premiumFeature.button.upgradeAccount": "Upgrade account",
113 "pricing.headline": "Support Franz", 122 "pricing.features.adFree": "Forever ad-free",
114 "pricing.link.skipPayment": "I don't want to support the development of Franz.", 123 "pricing.features.appDelays": "No Waiting Screens",
115 "pricing.submit.label": "I want to support the development of Franz", 124 "pricing.features.customWebsites": "Add Custom Websites",
116 "pricing.support.label": "Select your support plan", 125 "pricing.features.onPremise": "On-premise & other Hosted Services",
126 "pricing.features.serviceProxies": "Service Proxies",
127 "pricing.features.spellchecker": "Spellchecker support",
128 "pricing.features.teamManagement": "Team Management",
129 "pricing.features.thirdPartyServices": "Install 3rd party services",
130 "pricing.features.unlimitedServices": "Add unlimited services",
131 "pricing.features.workspaces": "Workspaces",
132 "pricing.plan.free": "Franz Free",
133 "pricing.plan.legacy": "Franz Premium",
134 "pricing.plan.personal": "Franz Personal",
135 "pricing.plan.personal-monthly": "Franz Personal Monthly",
136 "pricing.plan.personal-yearly": "Franz Personal Yearly",
137 "pricing.plan.pro": "Franz Professional",
138 "pricing.plan.pro-monthly": "Franz Professional Monthly",
139 "pricing.plan.pro-yearly": "Franz Professional Yearly",
140 "pricing.trial.cta.accept": "Yes, upgrade my account to Franz Professional",
141 "pricing.trial.cta.skip": "Continue to Franz",
142 "pricing.trial.error": "Sorry, we could not activate your trial!",
143 "pricing.trial.features.headline": "Franz Professional includes:",
144 "pricing.trial.headline": "Franz Professional",
145 "pricing.trial.subheadline": "Your personal welcome offer:",
146 "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days",
147 "pricing.trial.terms.headline": "No strings attached",
148 "pricing.trial.terms.noCreditCard": "No credit card required",
117 "service.crashHandler.action": "Reload {name}", 149 "service.crashHandler.action": "Reload {name}",
118 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", 150 "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds",
119 "service.crashHandler.headline": "Oh no!", 151 "service.crashHandler.headline": "Oh no!",
@@ -125,6 +157,11 @@
125 "service.errorHandler.headline": "Oh no!", 157 "service.errorHandler.headline": "Oh no!",
126 "service.errorHandler.message": "Error", 158 "service.errorHandler.message": "Error",
127 "service.errorHandler.text": "{name} has failed to load.", 159 "service.errorHandler.text": "{name} has failed to load.",
160 "service.restrictedHandler.action": "Upgrade Account",
161 "service.restrictedHandler.customUrl.headline": "Franz Professional Plan required",
162 "service.restrictedHandler.customUrl.text": "Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
163 "service.restrictedHandler.serviceLimit.headline": "You have reached your service limit.",
164 "service.restrictedHandler.serviceLimit.text": "Please upgrade your account to use more than {count} services.",
128 "service.webviewLoader.loading": "Loading", 165 "service.webviewLoader.loading": "Loading",
129 "services.getStarted": "Get started", 166 "services.getStarted": "Get started",
130 "services.welcome": "Welcome to Franz", 167 "services.welcome": "Welcome to Franz",
@@ -142,13 +179,19 @@
142 "settings.account.headlinePassword": "Change password", 179 "settings.account.headlinePassword": "Change password",
143 "settings.account.headlineProfile": "Update profile", 180 "settings.account.headlineProfile": "Update profile",
144 "settings.account.headlineSubscription": "Your subscription", 181 "settings.account.headlineSubscription": "Your subscription",
145 "settings.account.headlineUpgrade": "Upgrade your account & support Franz", 182 "settings.account.headlineTrialUpgrade": "Get the free 14 day Franz Professional Trial",
183 "settings.account.headlineUpgradeAccount": "Upgrade your account & get the full Franz experience",
146 "settings.account.invoiceDownload": "Download", 184 "settings.account.invoiceDownload": "Download",
147 "settings.account.manageSubscription.label": "Manage your subscription", 185 "settings.account.manageSubscription.label": "Manage your subscription",
148 "settings.account.successInfo": "Your changes have been saved", 186 "settings.account.successInfo": "Your changes have been saved",
187 "settings.account.trial": "Free Trial",
188 "settings.account.trialEndsIn": "Your free trial ends in {duration}.",
189 "settings.account.trialUpdateBillingInfo": "Please update your billing info to continue using {license} after your trial period.",
149 "settings.account.tryReloadServices": "Try again", 190 "settings.account.tryReloadServices": "Try again",
150 "settings.account.tryReloadUserInfoRequest": "Try again", 191 "settings.account.tryReloadUserInfoRequest": "Try again",
192 "settings.account.upgradeToPro.label": "Upgrade to Franz Professional",
151 "settings.account.userInfoRequestFailed": "Could not load user information", 193 "settings.account.userInfoRequestFailed": "Could not load user information",
194 "settings.account.yourLicense": "Your Franz License",
152 "settings.app.buttonClearAllCache": "Clear cache", 195 "settings.app.buttonClearAllCache": "Clear cache",
153 "settings.app.buttonInstallUpdate": "Restart & install update", 196 "settings.app.buttonInstallUpdate": "Restart & install update",
154 "settings.app.buttonSearchForUpdate": "Check for updates", 197 "settings.app.buttonSearchForUpdate": "Check for updates",
@@ -189,7 +232,13 @@
189 "settings.navigation.yourServices": "Your services", 232 "settings.navigation.yourServices": "Your services",
190 "settings.navigation.yourWorkspaces": "Your workspaces", 233 "settings.navigation.yourWorkspaces": "Your workspaces",
191 "settings.recipes.all": "All services", 234 "settings.recipes.all": "All services",
192 "settings.recipes.dev": "Development", 235 "settings.recipes.custom": "Custom Services",
236 "settings.recipes.customService.headline.communityRecipes": "Community Services",
237 "settings.recipes.customService.headline.customRecipes": "Custom Service Recipes",
238 "settings.recipes.customService.headline.devRecipes": "Your Development Service Recipes",
239 "settings.recipes.customService.intro": "To add a custom service, copy the service recipe to:",
240 "settings.recipes.customService.openDevDocs": "Developer Documentation",
241 "settings.recipes.customService.openFolder": "Open folder",
193 "settings.recipes.headline": "Available services", 242 "settings.recipes.headline": "Available services",
194 "settings.recipes.missingService": "Missing a service?", 243 "settings.recipes.missingService": "Missing a service?",
195 "settings.recipes.mostPopular": "Most popular", 244 "settings.recipes.mostPopular": "Most popular",
@@ -281,7 +330,6 @@
281 "sidebar.openWorkspaceDrawer": "Open workspace drawer", 330 "sidebar.openWorkspaceDrawer": "Open workspace drawer",
282 "sidebar.settings": "Settings", 331 "sidebar.settings": "Settings",
283 "sidebar.unmuteApp": "Enable notifications & audio", 332 "sidebar.unmuteApp": "Enable notifications & audio",
284 "signup.company.label": "Company",
285 "signup.email.label": "Email address", 333 "signup.email.label": "Email address",
286 "signup.emailDuplicate": "A user with that email address already exists", 334 "signup.emailDuplicate": "A user with that email address already exists",
287 "signup.firstname.label": "First Name", 335 "signup.firstname.label": "First Name",
@@ -293,20 +341,12 @@
293 "signup.link.login": "Already have an account, sign in?", 341 "signup.link.login": "Already have an account, sign in?",
294 "signup.password.label": "Password", 342 "signup.password.label": "Password",
295 "signup.submit.label": "Create account", 343 "signup.submit.label": "Create account",
296 "subscription.euTaxInfo": "EU residents: local sales tax may apply", 344 "subscription.cta.activateTrial": "Yes, start the free Franz Professional trial",
297 "subscription.features.ads": "No ads, ever!", 345 "subscription.cta.allOptions": "See all options",
298 "subscription.features.comingSoon": "coming soon", 346 "subscription.cta.choosePlan": "Choose your plan",
299 "subscription.features.noInterruptions": "No app delays & nagging to upgrade license", 347 "subscription.includedProFeatures": "The Franz Professional Plan includes:",
300 "subscription.features.onpremise.mattermost": "Add on-premise/hosted services like Mattermost", 348 "subscription.teaser.includedFeatures": "Paid Franz Plans include:",
301 "subscription.features.proxy": "Proxy support for services", 349 "subscription.teaser.intro": "Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
302 "subscription.features.spellchecker": "Support for spellchecker",
303 "subscription.features.workspaces": "Organize your services in workspaces",
304 "subscription.includedFeatures": "Paid Franz Premium Supporter Account includes",
305 "subscription.paymentSessionError": "Could not initialize payment form",
306 "subscription.submit.label": "I want to support the development of Franz",
307 "subscription.type.free": "free",
308 "subscription.type.month": "month",
309 "subscription.type.year": "year",
310 "subscriptionPopup.buttonCancel": "Cancel", 350 "subscriptionPopup.buttonCancel": "Cancel",
311 "subscriptionPopup.buttonDone": "Done", 351 "subscriptionPopup.buttonDone": "Done",
312 "tabs.item.deleteService": "Delete service", 352 "tabs.item.deleteService": "Delete service",
diff --git a/src/i18n/messages/src/components/TrialActivationInfoBar.json b/src/i18n/messages/src/components/TrialActivationInfoBar.json
new file mode 100644
index 000000000..65dd964a6
--- /dev/null
+++ b/src/i18n/messages/src/components/TrialActivationInfoBar.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "infobar.trialActivated",
4 "defaultMessage": "!!!Your trial was successfully activated. Happy messaging!",
5 "file": "src/components/TrialActivationInfoBar.js",
6 "start": {
7 "line": 11,
8 "column": 11
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json
index f711a55b4..f15617ca5 100644
--- a/src/i18n/messages/src/components/auth/Pricing.json
+++ b/src/i18n/messages/src/components/auth/Pricing.json
@@ -1,53 +1,118 @@
1[ 1[
2 { 2 {
3 "id": "pricing.headline", 3 "id": "pricing.trial.headline",
4 "defaultMessage": "!!!Support Franz", 4 "defaultMessage": "!!!Franz Professional",
5 "file": "src/components/auth/Pricing.js", 5 "file": "src/components/auth/Pricing.js",
6 "start": { 6 "start": {
7 "line": 13, 7 "line": 15,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 16, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
15 { 15 {
16 "id": "pricing.support.label", 16 "id": "pricing.trial.subheadline",
17 "defaultMessage": "!!!Select your support plan", 17 "defaultMessage": "!!!Your personal welcome offer:",
18 "file": "src/components/auth/Pricing.js", 18 "file": "src/components/auth/Pricing.js",
19 "start": { 19 "start": {
20 "line": 17, 20 "line": 19,
21 "column": 23 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 20, 24 "line": 22,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
28 { 28 {
29 "id": "pricing.submit.label", 29 "id": "pricing.trial.terms.headline",
30 "defaultMessage": "!!!Support the development of Franz", 30 "defaultMessage": "!!!No strings attached",
31 "file": "src/components/auth/Pricing.js", 31 "file": "src/components/auth/Pricing.js",
32 "start": { 32 "start": {
33 "line": 21, 33 "line": 23,
34 "column": 29
35 },
36 "end": {
37 "line": 26,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.trial.terms.noCreditCard",
43 "defaultMessage": "!!!No credit card required",
44 "file": "src/components/auth/Pricing.js",
45 "start": {
46 "line": 27,
47 "column": 16
48 },
49 "end": {
50 "line": 30,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.trial.terms.automaticTrialEnd",
56 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
57 "file": "src/components/auth/Pricing.js",
58 "start": {
59 "line": 31,
34 "column": 21 60 "column": 21
35 }, 61 },
36 "end": { 62 "end": {
37 "line": 24, 63 "line": 34,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.trial.error",
69 "defaultMessage": "!!!Sorry, we could not activate your trial!",
70 "file": "src/components/auth/Pricing.js",
71 "start": {
72 "line": 35,
73 "column": 19
74 },
75 "end": {
76 "line": 38,
77 "column": 3
78 }
79 },
80 {
81 "id": "pricing.trial.cta.accept",
82 "defaultMessage": "!!!Yes, upgrade my account to Franz Professional",
83 "file": "src/components/auth/Pricing.js",
84 "start": {
85 "line": 39,
86 "column": 13
87 },
88 "end": {
89 "line": 42,
90 "column": 3
91 }
92 },
93 {
94 "id": "pricing.trial.cta.skip",
95 "defaultMessage": "!!!Continue to Franz",
96 "file": "src/components/auth/Pricing.js",
97 "start": {
98 "line": 43,
99 "column": 11
100 },
101 "end": {
102 "line": 46,
38 "column": 3 103 "column": 3
39 } 104 }
40 }, 105 },
41 { 106 {
42 "id": "pricing.link.skipPayment", 107 "id": "pricing.trial.features.headline",
43 "defaultMessage": "!!!I don't want to support the development of Franz.", 108 "defaultMessage": "!!!Franz Professional includes:",
44 "file": "src/components/auth/Pricing.js", 109 "file": "src/components/auth/Pricing.js",
45 "start": { 110 "start": {
46 "line": 25, 111 "line": 47,
47 "column": 15 112 "column": 20
48 }, 113 },
49 "end": { 114 "end": {
50 "line": 28, 115 "line": 50,
51 "column": 3 116 "column": 3
52 } 117 }
53 } 118 }
diff --git a/src/i18n/messages/src/components/auth/Signup.json b/src/i18n/messages/src/components/auth/Signup.json
index a09745048..2ea71e5ff 100644
--- a/src/i18n/messages/src/components/auth/Signup.json
+++ b/src/i18n/messages/src/components/auth/Signup.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Sign up", 4 "defaultMessage": "!!!Sign up",
5 "file": "src/components/auth/Signup.js", 5 "file": "src/components/auth/Signup.js",
6 "start": { 6 "start": {
7 "line": 18, 7 "line": 17,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 21, 11 "line": 20,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Firstname", 17 "defaultMessage": "!!!Firstname",
18 "file": "src/components/auth/Signup.js", 18 "file": "src/components/auth/Signup.js",
19 "start": { 19 "start": {
20 "line": 22, 20 "line": 21,
21 "column": 18 21 "column": 18
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 25, 24 "line": 24,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Lastname", 30 "defaultMessage": "!!!Lastname",
31 "file": "src/components/auth/Signup.js", 31 "file": "src/components/auth/Signup.js",
32 "start": { 32 "start": {
33 "line": 26, 33 "line": 25,
34 "column": 17 34 "column": 17
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 29, 37 "line": 28,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,24 +43,11 @@
43 "defaultMessage": "!!!Email address", 43 "defaultMessage": "!!!Email address",
44 "file": "src/components/auth/Signup.js", 44 "file": "src/components/auth/Signup.js",
45 "start": { 45 "start": {
46 "line": 30, 46 "line": 29,
47 "column": 14 47 "column": 14
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 33, 50 "line": 32,
51 "column": 3
52 }
53 },
54 {
55 "id": "signup.company.label",
56 "defaultMessage": "!!!Company",
57 "file": "src/components/auth/Signup.js",
58 "start": {
59 "line": 34,
60 "column": 16
61 },
62 "end": {
63 "line": 37,
64 "column": 3 51 "column": 3
65 } 52 }
66 }, 53 },
@@ -69,11 +56,11 @@
69 "defaultMessage": "!!!Password", 56 "defaultMessage": "!!!Password",
70 "file": "src/components/auth/Signup.js", 57 "file": "src/components/auth/Signup.js",
71 "start": { 58 "start": {
72 "line": 38, 59 "line": 37,
73 "column": 17 60 "column": 17
74 }, 61 },
75 "end": { 62 "end": {
76 "line": 41, 63 "line": 40,
77 "column": 3 64 "column": 3
78 } 65 }
79 }, 66 },
@@ -82,11 +69,11 @@
82 "defaultMessage": "!!!By creating a Franz account you accept the", 69 "defaultMessage": "!!!By creating a Franz account you accept the",
83 "file": "src/components/auth/Signup.js", 70 "file": "src/components/auth/Signup.js",
84 "start": { 71 "start": {
85 "line": 42, 72 "line": 41,
86 "column": 13 73 "column": 13
87 }, 74 },
88 "end": { 75 "end": {
89 "line": 45, 76 "line": 44,
90 "column": 3 77 "column": 3
91 } 78 }
92 }, 79 },
@@ -95,11 +82,11 @@
95 "defaultMessage": "!!!Terms of service", 82 "defaultMessage": "!!!Terms of service",
96 "file": "src/components/auth/Signup.js", 83 "file": "src/components/auth/Signup.js",
97 "start": { 84 "start": {
98 "line": 46, 85 "line": 45,
99 "column": 9 86 "column": 9
100 }, 87 },
101 "end": { 88 "end": {
102 "line": 49, 89 "line": 48,
103 "column": 3 90 "column": 3
104 } 91 }
105 }, 92 },
@@ -108,11 +95,11 @@
108 "defaultMessage": "!!!Privacy Statement", 95 "defaultMessage": "!!!Privacy Statement",
109 "file": "src/components/auth/Signup.js", 96 "file": "src/components/auth/Signup.js",
110 "start": { 97 "start": {
111 "line": 50, 98 "line": 49,
112 "column": 11 99 "column": 11
113 }, 100 },
114 "end": { 101 "end": {
115 "line": 53, 102 "line": 52,
116 "column": 3 103 "column": 3
117 } 104 }
118 }, 105 },
@@ -121,11 +108,11 @@
121 "defaultMessage": "!!!Create account", 108 "defaultMessage": "!!!Create account",
122 "file": "src/components/auth/Signup.js", 109 "file": "src/components/auth/Signup.js",
123 "start": { 110 "start": {
124 "line": 54, 111 "line": 53,
125 "column": 21 112 "column": 21
126 }, 113 },
127 "end": { 114 "end": {
128 "line": 57, 115 "line": 56,
129 "column": 3 116 "column": 3
130 } 117 }
131 }, 118 },
@@ -134,11 +121,11 @@
134 "defaultMessage": "!!!Already have an account, sign in?", 121 "defaultMessage": "!!!Already have an account, sign in?",
135 "file": "src/components/auth/Signup.js", 122 "file": "src/components/auth/Signup.js",
136 "start": { 123 "start": {
137 "line": 58, 124 "line": 57,
138 "column": 13 125 "column": 13
139 }, 126 },
140 "end": { 127 "end": {
141 "line": 61, 128 "line": 60,
142 "column": 3 129 "column": 3
143 } 130 }
144 }, 131 },
@@ -147,11 +134,11 @@
147 "defaultMessage": "!!!A user with that email address already exists", 134 "defaultMessage": "!!!A user with that email address already exists",
148 "file": "src/components/auth/Signup.js", 135 "file": "src/components/auth/Signup.js",
149 "start": { 136 "start": {
150 "line": 62, 137 "line": 61,
151 "column": 18 138 "column": 18
152 }, 139 },
153 "end": { 140 "end": {
154 "line": 65, 141 "line": 64,
155 "column": 3 142 "column": 3
156 } 143 }
157 } 144 }
diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json
index b71889155..44cf4fab9 100644
--- a/src/i18n/messages/src/components/layout/AppLayout.json
+++ b/src/i18n/messages/src/components/layout/AppLayout.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services have been updated.", 4 "defaultMessage": "!!!Your services have been updated.",
5 "file": "src/components/layout/AppLayout.js", 5 "file": "src/components/layout/AppLayout.js",
6 "start": { 6 "start": {
7 "line": 27, 7 "line": 28,
8 "column": 19 8 "column": 19
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 30, 11 "line": 31,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Reload services", 17 "defaultMessage": "!!!Reload services",
18 "file": "src/components/layout/AppLayout.js", 18 "file": "src/components/layout/AppLayout.js",
19 "start": { 19 "start": {
20 "line": 31, 20 "line": 32,
21 "column": 24 21 "column": 24
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 34, 24 "line": 35,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Could not load services and user information", 30 "defaultMessage": "!!!Could not load services and user information",
31 "file": "src/components/layout/AppLayout.js", 31 "file": "src/components/layout/AppLayout.js",
32 "start": { 32 "start": {
33 "line": 35, 33 "line": 36,
34 "column": 26 34 "column": 26
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 38, 37 "line": 39,
38 "column": 3 38 "column": 3
39 } 39 }
40 } 40 }
diff --git a/src/i18n/messages/src/components/services/content/ServiceRestricted.json b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
new file mode 100644
index 000000000..c1984afe3
--- /dev/null
+++ b/src/i18n/messages/src/components/services/content/ServiceRestricted.json
@@ -0,0 +1,67 @@
1[
2 {
3 "id": "service.restrictedHandler.serviceLimit.headline",
4 "defaultMessage": "!!!You have reached your service limit.",
5 "file": "src/components/services/content/ServiceRestricted.js",
6 "start": {
7 "line": 11,
8 "column": 24
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "service.restrictedHandler.serviceLimit.text",
17 "defaultMessage": "!!!Please upgrade your account to use more than {count} services.",
18 "file": "src/components/services/content/ServiceRestricted.js",
19 "start": {
20 "line": 15,
21 "column": 20
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 },
28 {
29 "id": "service.restrictedHandler.customUrl.headline",
30 "defaultMessage": "!!!Franz Professional Plan required",
31 "file": "src/components/services/content/ServiceRestricted.js",
32 "start": {
33 "line": 19,
34 "column": 21
35 },
36 "end": {
37 "line": 22,
38 "column": 3
39 }
40 },
41 {
42 "id": "service.restrictedHandler.customUrl.text",
43 "defaultMessage": "!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.",
44 "file": "src/components/services/content/ServiceRestricted.js",
45 "start": {
46 "line": 23,
47 "column": 17
48 },
49 "end": {
50 "line": 26,
51 "column": 3
52 }
53 },
54 {
55 "id": "service.restrictedHandler.action",
56 "defaultMessage": "!!!Upgrade Account",
57 "file": "src/components/services/content/ServiceRestricted.js",
58 "start": {
59 "line": 27,
60 "column": 10
61 },
62 "end": {
63 "line": 30,
64 "column": 3
65 }
66 }
67] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/services/content/Services.json b/src/i18n/messages/src/components/services/content/Services.json
index 884ab0c90..eb466c0ac 100644
--- a/src/i18n/messages/src/components/services/content/Services.json
+++ b/src/i18n/messages/src/components/services/content/Services.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Welcome to Franz", 4 "defaultMessage": "!!!Welcome to Franz",
5 "file": "src/components/services/content/Services.js", 5 "file": "src/components/services/content/Services.js",
6 "start": { 6 "start": {
7 "line": 11, 7 "line": 14,
8 "column": 11 8 "column": 11
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 14, 11 "line": 17,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Get started", 17 "defaultMessage": "!!!Get started",
18 "file": "src/components/services/content/Services.js", 18 "file": "src/components/services/content/Services.js",
19 "start": { 19 "start": {
20 "line": 15, 20 "line": 18,
21 "column": 14 21 "column": 14
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 18, 24 "line": 21,
25 "column": 3 25 "column": 3
26 } 26 }
27 } 27 }
diff --git a/src/i18n/messages/src/components/settings/account/AccountDashboard.json b/src/i18n/messages/src/components/settings/account/AccountDashboard.json
index 4969db910..06d53e41d 100644
--- a/src/i18n/messages/src/components/settings/account/AccountDashboard.json
+++ b/src/i18n/messages/src/components/settings/account/AccountDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Account", 4 "defaultMessage": "!!!Account",
5 "file": "src/components/settings/account/AccountDashboard.js", 5 "file": "src/components/settings/account/AccountDashboard.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 18,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 21,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,21 +17,8 @@
17 "defaultMessage": "!!!Your Subscription", 17 "defaultMessage": "!!!Your Subscription",
18 "file": "src/components/settings/account/AccountDashboard.js", 18 "file": "src/components/settings/account/AccountDashboard.js",
19 "start": { 19 "start": {
20 "line": 18,
21 "column": 24
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.account.headlineUpgrade",
30 "defaultMessage": "!!!Upgrade your Account",
31 "file": "src/components/settings/account/AccountDashboard.js",
32 "start": {
33 "line": 22, 20 "line": 22,
34 "column": 19 21 "column": 24
35 }, 22 },
36 "end": { 23 "end": {
37 "line": 25, 24 "line": 25,
@@ -65,15 +52,28 @@
65 } 52 }
66 }, 53 },
67 { 54 {
55 "id": "settings.account.upgradeToPro.label",
56 "defaultMessage": "!!!Upgrade to Franz Professional",
57 "file": "src/components/settings/account/AccountDashboard.js",
58 "start": {
59 "line": 34,
60 "column": 23
61 },
62 "end": {
63 "line": 37,
64 "column": 3
65 }
66 },
67 {
68 "id": "settings.account.accountType.basic", 68 "id": "settings.account.accountType.basic",
69 "defaultMessage": "!!!Basic Account", 69 "defaultMessage": "!!!Basic Account",
70 "file": "src/components/settings/account/AccountDashboard.js", 70 "file": "src/components/settings/account/AccountDashboard.js",
71 "start": { 71 "start": {
72 "line": 34, 72 "line": 38,
73 "column": 20 73 "column": 20
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 37, 76 "line": 41,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Premium Supporter Account", 82 "defaultMessage": "!!!Premium Supporter Account",
83 "file": "src/components/settings/account/AccountDashboard.js", 83 "file": "src/components/settings/account/AccountDashboard.js",
84 "start": { 84 "start": {
85 "line": 38, 85 "line": 42,
86 "column": 22 86 "column": 22
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 41, 89 "line": 45,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Edit Account", 95 "defaultMessage": "!!!Edit Account",
96 "file": "src/components/settings/account/AccountDashboard.js", 96 "file": "src/components/settings/account/AccountDashboard.js",
97 "start": { 97 "start": {
98 "line": 42, 98 "line": 46,
99 "column": 21 99 "column": 21
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 45, 102 "line": 49,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!Invoices", 108 "defaultMessage": "!!Invoices",
109 "file": "src/components/settings/account/AccountDashboard.js", 109 "file": "src/components/settings/account/AccountDashboard.js",
110 "start": { 110 "start": {
111 "line": 46, 111 "line": 50,
112 "column": 18 112 "column": 18
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 49, 115 "line": 53,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Download", 121 "defaultMessage": "!!!Download",
122 "file": "src/components/settings/account/AccountDashboard.js", 122 "file": "src/components/settings/account/AccountDashboard.js",
123 "start": { 123 "start": {
124 "line": 50, 124 "line": 54,
125 "column": 19 125 "column": 19
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 53, 128 "line": 57,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!Could not load user information", 134 "defaultMessage": "!!!Could not load user information",
135 "file": "src/components/settings/account/AccountDashboard.js", 135 "file": "src/components/settings/account/AccountDashboard.js",
136 "start": { 136 "start": {
137 "line": 54, 137 "line": 58,
138 "column": 25 138 "column": 25
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 57, 141 "line": 61,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Try again", 147 "defaultMessage": "!!!Try again",
148 "file": "src/components/settings/account/AccountDashboard.js", 148 "file": "src/components/settings/account/AccountDashboard.js",
149 "start": { 149 "start": {
150 "line": 58, 150 "line": 62,
151 "column": 28 151 "column": 28
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 61, 154 "line": 65,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!Delete account", 160 "defaultMessage": "!!!Delete account",
161 "file": "src/components/settings/account/AccountDashboard.js", 161 "file": "src/components/settings/account/AccountDashboard.js",
162 "start": { 162 "start": {
163 "line": 62, 163 "line": 66,
164 "column": 17 164 "column": 17
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 65, 167 "line": 69,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.", 173 "defaultMessage": "!!!If you don't need your Franz account any longer, you can delete your account and all related data here.",
174 "file": "src/components/settings/account/AccountDashboard.js", 174 "file": "src/components/settings/account/AccountDashboard.js",
175 "start": { 175 "start": {
176 "line": 66, 176 "line": 70,
177 "column": 14 177 "column": 14
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 69, 180 "line": 73,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,63 @@
186 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!", 186 "defaultMessage": "!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!",
187 "file": "src/components/settings/account/AccountDashboard.js", 187 "file": "src/components/settings/account/AccountDashboard.js",
188 "start": { 188 "start": {
189 "line": 70, 189 "line": 74,
190 "column": 19 190 "column": 19
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 73, 193 "line": 77,
194 "column": 3
195 }
196 },
197 {
198 "id": "settings.account.trial",
199 "defaultMessage": "!!!Free Trial",
200 "file": "src/components/settings/account/AccountDashboard.js",
201 "start": {
202 "line": 78,
203 "column": 9
204 },
205 "end": {
206 "line": 81,
207 "column": 3
208 }
209 },
210 {
211 "id": "settings.account.yourLicense",
212 "defaultMessage": "!!!Your Franz License:",
213 "file": "src/components/settings/account/AccountDashboard.js",
214 "start": {
215 "line": 82,
216 "column": 15
217 },
218 "end": {
219 "line": 85,
220 "column": 3
221 }
222 },
223 {
224 "id": "settings.account.trialEndsIn",
225 "defaultMessage": "!!!Your free trial ends in {duration}.",
226 "file": "src/components/settings/account/AccountDashboard.js",
227 "start": {
228 "line": 86,
229 "column": 15
230 },
231 "end": {
232 "line": 89,
233 "column": 3
234 }
235 },
236 {
237 "id": "settings.account.trialUpdateBillingInfo",
238 "defaultMessage": "!!!Please update your billing info to continue using {license} after your trial period.",
239 "file": "src/components/settings/account/AccountDashboard.js",
240 "start": {
241 "line": 90,
242 "column": 33
243 },
244 "end": {
245 "line": 93,
194 "column": 3 246 "column": 3
195 } 247 }
196 } 248 }
diff --git a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
index 70a989211..7dfb3ce04 100644
--- a/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
+++ b/src/i18n/messages/src/components/settings/navigation/SettingsNavigation.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Available services", 4 "defaultMessage": "!!!Available services",
5 "file": "src/components/settings/navigation/SettingsNavigation.js", 5 "file": "src/components/settings/navigation/SettingsNavigation.js",
6 "start": { 6 "start": {
7 "line": 13, 7 "line": 14,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 16, 11 "line": 17,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Your services", 17 "defaultMessage": "!!!Your services",
18 "file": "src/components/settings/navigation/SettingsNavigation.js", 18 "file": "src/components/settings/navigation/SettingsNavigation.js",
19 "start": { 19 "start": {
20 "line": 17, 20 "line": 18,
21 "column": 16 21 "column": 16
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 20, 24 "line": 21,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Your workspaces", 30 "defaultMessage": "!!!Your workspaces",
31 "file": "src/components/settings/navigation/SettingsNavigation.js", 31 "file": "src/components/settings/navigation/SettingsNavigation.js",
32 "start": { 32 "start": {
33 "line": 21, 33 "line": 22,
34 "column": 18 34 "column": 18
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 24, 37 "line": 25,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Account", 43 "defaultMessage": "!!!Account",
44 "file": "src/components/settings/navigation/SettingsNavigation.js", 44 "file": "src/components/settings/navigation/SettingsNavigation.js",
45 "start": { 45 "start": {
46 "line": 25, 46 "line": 26,
47 "column": 11 47 "column": 11
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 28, 50 "line": 29,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Manage Team", 56 "defaultMessage": "!!!Manage Team",
57 "file": "src/components/settings/navigation/SettingsNavigation.js", 57 "file": "src/components/settings/navigation/SettingsNavigation.js",
58 "start": { 58 "start": {
59 "line": 29, 59 "line": 30,
60 "column": 8 60 "column": 8
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 32, 63 "line": 33,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Settings", 69 "defaultMessage": "!!!Settings",
70 "file": "src/components/settings/navigation/SettingsNavigation.js", 70 "file": "src/components/settings/navigation/SettingsNavigation.js",
71 "start": { 71 "start": {
72 "line": 33, 72 "line": 34,
73 "column": 12 73 "column": 12
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 36, 76 "line": 37,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Invite Friends", 82 "defaultMessage": "!!!Invite Friends",
83 "file": "src/components/settings/navigation/SettingsNavigation.js", 83 "file": "src/components/settings/navigation/SettingsNavigation.js",
84 "start": { 84 "start": {
85 "line": 37, 85 "line": 38,
86 "column": 17 86 "column": 17
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 40, 89 "line": 41,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Logout", 95 "defaultMessage": "!!!Logout",
96 "file": "src/components/settings/navigation/SettingsNavigation.js", 96 "file": "src/components/settings/navigation/SettingsNavigation.js",
97 "start": { 97 "start": {
98 "line": 41, 98 "line": 42,
99 "column": 10 99 "column": 10
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 44, 102 "line": 45,
103 "column": 3 103 "column": 3
104 } 104 }
105 } 105 }
diff --git a/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json b/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
index 7d9ed3283..8afaaed50 100644
--- a/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
+++ b/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Available Services", 4 "defaultMessage": "!!!Available Services",
5 "file": "src/components/settings/recipes/RecipesDashboard.js", 5 "file": "src/components/settings/recipes/RecipesDashboard.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 20,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 23,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Search service", 17 "defaultMessage": "!!!Search service",
18 "file": "src/components/settings/recipes/RecipesDashboard.js", 18 "file": "src/components/settings/recipes/RecipesDashboard.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 24,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 27,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Most popular", 30 "defaultMessage": "!!!Most popular",
31 "file": "src/components/settings/recipes/RecipesDashboard.js", 31 "file": "src/components/settings/recipes/RecipesDashboard.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 28,
34 "column": 22 34 "column": 22
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 31,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,24 +43,24 @@
43 "defaultMessage": "!!!All services", 43 "defaultMessage": "!!!All services",
44 "file": "src/components/settings/recipes/RecipesDashboard.js", 44 "file": "src/components/settings/recipes/RecipesDashboard.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 32,
47 "column": 14 47 "column": 14
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 35,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
54 { 54 {
55 "id": "settings.recipes.dev", 55 "id": "settings.recipes.custom",
56 "defaultMessage": "!!!Development", 56 "defaultMessage": "!!!Custom Services",
57 "file": "src/components/settings/recipes/RecipesDashboard.js", 57 "file": "src/components/settings/recipes/RecipesDashboard.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 36,
60 "column": 14 60 "column": 17
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 39,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Sorry, but no service matched your search term.", 69 "defaultMessage": "!!!Sorry, but no service matched your search term.",
70 "file": "src/components/settings/recipes/RecipesDashboard.js", 70 "file": "src/components/settings/recipes/RecipesDashboard.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 40,
73 "column": 16 73 "column": 16
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 43,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Service successfully added", 82 "defaultMessage": "!!!Service successfully added",
83 "file": "src/components/settings/recipes/RecipesDashboard.js", 83 "file": "src/components/settings/recipes/RecipesDashboard.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 44,
86 "column": 31 86 "column": 31
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 47,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,89 @@
95 "defaultMessage": "!!!Missing a service?", 95 "defaultMessage": "!!!Missing a service?",
96 "file": "src/components/settings/recipes/RecipesDashboard.js", 96 "file": "src/components/settings/recipes/RecipesDashboard.js",
97 "start": { 97 "start": {
98 "line": 43, 98 "line": 48,
99 "column": 18 99 "column": 18
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 46, 102 "line": 51,
103 "column": 3
104 }
105 },
106 {
107 "id": "settings.recipes.customService.intro",
108 "defaultMessage": "!!!To add a custom service, copy the recipe folder into:",
109 "file": "src/components/settings/recipes/RecipesDashboard.js",
110 "start": {
111 "line": 52,
112 "column": 21
113 },
114 "end": {
115 "line": 55,
116 "column": 3
117 }
118 },
119 {
120 "id": "settings.recipes.customService.openFolder",
121 "defaultMessage": "!!!Open directory",
122 "file": "src/components/settings/recipes/RecipesDashboard.js",
123 "start": {
124 "line": 56,
125 "column": 14
126 },
127 "end": {
128 "line": 59,
129 "column": 3
130 }
131 },
132 {
133 "id": "settings.recipes.customService.openDevDocs",
134 "defaultMessage": "!!!Developer Documentation",
135 "file": "src/components/settings/recipes/RecipesDashboard.js",
136 "start": {
137 "line": 60,
138 "column": 15
139 },
140 "end": {
141 "line": 63,
142 "column": 3
143 }
144 },
145 {
146 "id": "settings.recipes.customService.headline.customRecipes",
147 "defaultMessage": "!!!Custom Service Recipes",
148 "file": "src/components/settings/recipes/RecipesDashboard.js",
149 "start": {
150 "line": 64,
151 "column": 25
152 },
153 "end": {
154 "line": 67,
155 "column": 3
156 }
157 },
158 {
159 "id": "settings.recipes.customService.headline.communityRecipes",
160 "defaultMessage": "!!!Community Services",
161 "file": "src/components/settings/recipes/RecipesDashboard.js",
162 "start": {
163 "line": 68,
164 "column": 28
165 },
166 "end": {
167 "line": 71,
168 "column": 3
169 }
170 },
171 {
172 "id": "settings.recipes.customService.headline.devRecipes",
173 "defaultMessage": "!!!Your Development Service Recipes",
174 "file": "src/components/settings/recipes/RecipesDashboard.js",
175 "start": {
176 "line": 72,
177 "column": 22
178 },
179 "end": {
180 "line": 75,
103 "column": 3 181 "column": 3
104 } 182 }
105 } 183 }
diff --git a/src/i18n/messages/src/components/settings/services/EditServiceForm.json b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
index 42b741b7a..e66db807d 100644
--- a/src/i18n/messages/src/components/settings/services/EditServiceForm.json
+++ b/src/i18n/messages/src/components/settings/services/EditServiceForm.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Save service", 4 "defaultMessage": "!!!Save service",
5 "file": "src/components/settings/services/EditServiceForm.js", 5 "file": "src/components/settings/services/EditServiceForm.js",
6 "start": { 6 "start": {
7 "line": 22, 7 "line": 24,
8 "column": 15 8 "column": 15
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 25, 11 "line": 27,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Delete Service", 17 "defaultMessage": "!!!Delete Service",
18 "file": "src/components/settings/services/EditServiceForm.js", 18 "file": "src/components/settings/services/EditServiceForm.js",
19 "start": { 19 "start": {
20 "line": 26, 20 "line": 28,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 29, 24 "line": 31,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Available services", 30 "defaultMessage": "!!!Available services",
31 "file": "src/components/settings/services/EditServiceForm.js", 31 "file": "src/components/settings/services/EditServiceForm.js",
32 "start": { 32 "start": {
33 "line": 30, 33 "line": 32,
34 "column": 21 34 "column": 21
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 33, 37 "line": 35,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Your services", 43 "defaultMessage": "!!!Your services",
44 "file": "src/components/settings/services/EditServiceForm.js", 44 "file": "src/components/settings/services/EditServiceForm.js",
45 "start": { 45 "start": {
46 "line": 34, 46 "line": 36,
47 "column": 16 47 "column": 16
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 37, 50 "line": 39,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Add {name}", 56 "defaultMessage": "!!!Add {name}",
57 "file": "src/components/settings/services/EditServiceForm.js", 57 "file": "src/components/settings/services/EditServiceForm.js",
58 "start": { 58 "start": {
59 "line": 38, 59 "line": 40,
60 "column": 22 60 "column": 22
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 41, 63 "line": 43,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Edit {name}", 69 "defaultMessage": "!!!Edit {name}",
70 "file": "src/components/settings/services/EditServiceForm.js", 70 "file": "src/components/settings/services/EditServiceForm.js",
71 "start": { 71 "start": {
72 "line": 42, 72 "line": 44,
73 "column": 23 73 "column": 23
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 45, 76 "line": 47,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Hosted", 82 "defaultMessage": "!!!Hosted",
83 "file": "src/components/settings/services/EditServiceForm.js", 83 "file": "src/components/settings/services/EditServiceForm.js",
84 "start": { 84 "start": {
85 "line": 46, 85 "line": 48,
86 "column": 13 86 "column": 13
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 49, 89 "line": 51,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Self hosted ⭐️", 95 "defaultMessage": "!!!Self hosted ⭐️",
96 "file": "src/components/settings/services/EditServiceForm.js", 96 "file": "src/components/settings/services/EditServiceForm.js",
97 "start": { 97 "start": {
98 "line": 50, 98 "line": 52,
99 "column": 16 99 "column": 16
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 53, 102 "line": 55,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Use the hosted {name} service.", 108 "defaultMessage": "!!!Use the hosted {name} service.",
109 "file": "src/components/settings/services/EditServiceForm.js", 109 "file": "src/components/settings/services/EditServiceForm.js",
110 "start": { 110 "start": {
111 "line": 54, 111 "line": 56,
112 "column": 20 112 "column": 20
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 57, 115 "line": 59,
116 "column": 3 116 "column": 3
117 } 117 }
118 }, 118 },
@@ -121,11 +121,11 @@
121 "defaultMessage": "!!!Could not validate custom {name} server.", 121 "defaultMessage": "!!!Could not validate custom {name} server.",
122 "file": "src/components/settings/services/EditServiceForm.js", 122 "file": "src/components/settings/services/EditServiceForm.js",
123 "start": { 123 "start": {
124 "line": 58, 124 "line": 60,
125 "column": 28 125 "column": 28
126 }, 126 },
127 "end": { 127 "end": {
128 "line": 61, 128 "line": 63,
129 "column": 3 129 "column": 3
130 } 130 }
131 }, 131 },
@@ -134,11 +134,11 @@
134 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", 134 "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.",
135 "file": "src/components/settings/services/EditServiceForm.js", 135 "file": "src/components/settings/services/EditServiceForm.js",
136 "start": { 136 "start": {
137 "line": 62, 137 "line": 64,
138 "column": 24 138 "column": 24
139 }, 139 },
140 "end": { 140 "end": {
141 "line": 65, 141 "line": 67,
142 "column": 3 142 "column": 3
143 } 143 }
144 }, 144 },
@@ -147,11 +147,11 @@
147 "defaultMessage": "!!!Upgrade your account", 147 "defaultMessage": "!!!Upgrade your account",
148 "file": "src/components/settings/services/EditServiceForm.js", 148 "file": "src/components/settings/services/EditServiceForm.js",
149 "start": { 149 "start": {
150 "line": 66, 150 "line": 68,
151 "column": 27 151 "column": 27
152 }, 152 },
153 "end": { 153 "end": {
154 "line": 69, 154 "line": 71,
155 "column": 3 155 "column": 3
156 } 156 }
157 }, 157 },
@@ -160,11 +160,11 @@
160 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", 160 "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...",
161 "file": "src/components/settings/services/EditServiceForm.js", 161 "file": "src/components/settings/services/EditServiceForm.js",
162 "start": { 162 "start": {
163 "line": 70, 163 "line": 72,
164 "column": 23 164 "column": 23
165 }, 165 },
166 "end": { 166 "end": {
167 "line": 73, 167 "line": 75,
168 "column": 3 168 "column": 3
169 } 169 }
170 }, 170 },
@@ -173,11 +173,11 @@
173 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", 173 "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted",
174 "file": "src/components/settings/services/EditServiceForm.js", 174 "file": "src/components/settings/services/EditServiceForm.js",
175 "start": { 175 "start": {
176 "line": 74, 176 "line": 76,
177 "column": 15 177 "column": 15
178 }, 178 },
179 "end": { 179 "end": {
180 "line": 77, 180 "line": 79,
181 "column": 3 181 "column": 3
182 } 182 }
183 }, 183 },
@@ -186,11 +186,11 @@
186 "defaultMessage": "!!!Notifications", 186 "defaultMessage": "!!!Notifications",
187 "file": "src/components/settings/services/EditServiceForm.js", 187 "file": "src/components/settings/services/EditServiceForm.js",
188 "start": { 188 "start": {
189 "line": 78, 189 "line": 80,
190 "column": 25 190 "column": 25
191 }, 191 },
192 "end": { 192 "end": {
193 "line": 81, 193 "line": 83,
194 "column": 3 194 "column": 3
195 } 195 }
196 }, 196 },
@@ -199,11 +199,11 @@
199 "defaultMessage": "!!!Unread message badges", 199 "defaultMessage": "!!!Unread message badges",
200 "file": "src/components/settings/services/EditServiceForm.js", 200 "file": "src/components/settings/services/EditServiceForm.js",
201 "start": { 201 "start": {
202 "line": 82, 202 "line": 84,
203 "column": 18 203 "column": 18
204 }, 204 },
205 "end": { 205 "end": {
206 "line": 85, 206 "line": 87,
207 "column": 3 207 "column": 3
208 } 208 }
209 }, 209 },
@@ -212,11 +212,11 @@
212 "defaultMessage": "!!!General", 212 "defaultMessage": "!!!General",
213 "file": "src/components/settings/services/EditServiceForm.js", 213 "file": "src/components/settings/services/EditServiceForm.js",
214 "start": { 214 "start": {
215 "line": 86, 215 "line": 88,
216 "column": 19 216 "column": 19
217 }, 217 },
218 "end": { 218 "end": {
219 "line": 89, 219 "line": 91,
220 "column": 3 220 "column": 3
221 } 221 }
222 }, 222 },
@@ -225,11 +225,11 @@
225 "defaultMessage": "!!!Delete", 225 "defaultMessage": "!!!Delete",
226 "file": "src/components/settings/services/EditServiceForm.js", 226 "file": "src/components/settings/services/EditServiceForm.js",
227 "start": { 227 "start": {
228 "line": 90, 228 "line": 92,
229 "column": 14 229 "column": 14
230 }, 230 },
231 "end": { 231 "end": {
232 "line": 93, 232 "line": 95,
233 "column": 3 233 "column": 3
234 } 234 }
235 }, 235 },
@@ -238,11 +238,11 @@
238 "defaultMessage": "!!!Drop your image, or click here", 238 "defaultMessage": "!!!Drop your image, or click here",
239 "file": "src/components/settings/services/EditServiceForm.js", 239 "file": "src/components/settings/services/EditServiceForm.js",
240 "start": { 240 "start": {
241 "line": 94, 241 "line": 96,
242 "column": 14 242 "column": 14
243 }, 243 },
244 "end": { 244 "end": {
245 "line": 97, 245 "line": 99,
246 "column": 3 246 "column": 3
247 } 247 }
248 }, 248 },
@@ -251,11 +251,11 @@
251 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", 251 "defaultMessage": "!!!HTTP/HTTPS Proxy Settings",
252 "file": "src/components/settings/services/EditServiceForm.js", 252 "file": "src/components/settings/services/EditServiceForm.js",
253 "start": { 253 "start": {
254 "line": 98, 254 "line": 100,
255 "column": 17 255 "column": 17
256 }, 256 },
257 "end": { 257 "end": {
258 "line": 101, 258 "line": 103,
259 "column": 3 259 "column": 3
260 } 260 }
261 }, 261 },
@@ -264,11 +264,11 @@
264 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", 264 "defaultMessage": "!!!Please restart Franz after changing proxy Settings.",
265 "file": "src/components/settings/services/EditServiceForm.js", 265 "file": "src/components/settings/services/EditServiceForm.js",
266 "start": { 266 "start": {
267 "line": 102, 267 "line": 104,
268 "column": 20 268 "column": 20
269 }, 269 },
270 "end": { 270 "end": {
271 "line": 105, 271 "line": 107,
272 "column": 3 272 "column": 3
273 } 273 }
274 }, 274 },
@@ -277,11 +277,11 @@
277 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", 277 "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.",
278 "file": "src/components/settings/services/EditServiceForm.js", 278 "file": "src/components/settings/services/EditServiceForm.js",
279 "start": { 279 "start": {
280 "line": 106, 280 "line": 108,
281 "column": 13 281 "column": 13
282 }, 282 },
283 "end": { 283 "end": {
284 "line": 109, 284 "line": 111,
285 "column": 3 285 "column": 3
286 } 286 }
287 } 287 }
diff --git a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
index 3803c6512..fa661ea2f 100644
--- a/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
+++ b/src/i18n/messages/src/components/settings/services/ServicesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your services", 4 "defaultMessage": "!!!Your services",
5 "file": "src/components/settings/services/ServicesDashboard.js", 5 "file": "src/components/settings/services/ServicesDashboard.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 15,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 18,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Search service", 17 "defaultMessage": "!!!Search service",
18 "file": "src/components/settings/services/ServicesDashboard.js", 18 "file": "src/components/settings/services/ServicesDashboard.js",
19 "start": { 19 "start": {
20 "line": 18, 20 "line": 19,
21 "column": 17 21 "column": 17
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 21, 24 "line": 22,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!You haven't added any services yet.", 30 "defaultMessage": "!!!You haven't added any services yet.",
31 "file": "src/components/settings/services/ServicesDashboard.js", 31 "file": "src/components/settings/services/ServicesDashboard.js",
32 "start": { 32 "start": {
33 "line": 22, 33 "line": 23,
34 "column": 19 34 "column": 19
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 25, 37 "line": 26,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Sorry, but no service matched your search term.", 43 "defaultMessage": "!!!Sorry, but no service matched your search term.",
44 "file": "src/components/settings/services/ServicesDashboard.js", 44 "file": "src/components/settings/services/ServicesDashboard.js",
45 "start": { 45 "start": {
46 "line": 26, 46 "line": 27,
47 "column": 18 47 "column": 18
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 29, 50 "line": 30,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Discover services", 56 "defaultMessage": "!!!Discover services",
57 "file": "src/components/settings/services/ServicesDashboard.js", 57 "file": "src/components/settings/services/ServicesDashboard.js",
58 "start": { 58 "start": {
59 "line": 30, 59 "line": 31,
60 "column": 20 60 "column": 20
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 33, 63 "line": 34,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Could not load your services", 69 "defaultMessage": "!!!Could not load your services",
70 "file": "src/components/settings/services/ServicesDashboard.js", 70 "file": "src/components/settings/services/ServicesDashboard.js",
71 "start": { 71 "start": {
72 "line": 34, 72 "line": 35,
73 "column": 25 73 "column": 25
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 37, 76 "line": 38,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Try again", 82 "defaultMessage": "!!!Try again",
83 "file": "src/components/settings/services/ServicesDashboard.js", 83 "file": "src/components/settings/services/ServicesDashboard.js",
84 "start": { 84 "start": {
85 "line": 38, 85 "line": 39,
86 "column": 21 86 "column": 21
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 41, 89 "line": 42,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Your changes have been saved", 95 "defaultMessage": "!!!Your changes have been saved",
96 "file": "src/components/settings/services/ServicesDashboard.js", 96 "file": "src/components/settings/services/ServicesDashboard.js",
97 "start": { 97 "start": {
98 "line": 42, 98 "line": 43,
99 "column": 15 99 "column": 15
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 45, 102 "line": 46,
103 "column": 3 103 "column": 3
104 } 104 }
105 }, 105 },
@@ -108,11 +108,11 @@
108 "defaultMessage": "!!!Service has been deleted", 108 "defaultMessage": "!!!Service has been deleted",
109 "file": "src/components/settings/services/ServicesDashboard.js", 109 "file": "src/components/settings/services/ServicesDashboard.js",
110 "start": { 110 "start": {
111 "line": 46, 111 "line": 47,
112 "column": 15 112 "column": 15
113 }, 113 },
114 "end": { 114 "end": {
115 "line": 49, 115 "line": 50,
116 "column": 3 116 "column": 3
117 } 117 }
118 } 118 }
diff --git a/src/i18n/messages/src/components/subscription/SubscriptionForm.json b/src/i18n/messages/src/components/subscription/SubscriptionForm.json
index f98eb986f..6d235254e 100644
--- a/src/i18n/messages/src/components/subscription/SubscriptionForm.json
+++ b/src/i18n/messages/src/components/subscription/SubscriptionForm.json
@@ -1,183 +1,53 @@
1[ 1[
2 { 2 {
3 "id": "subscription.submit.label", 3 "id": "subscription.cta.choosePlan",
4 "defaultMessage": "!!!Support the development of Franz", 4 "defaultMessage": "!!!Choose your plan",
5 "file": "src/components/subscription/SubscriptionForm.js", 5 "file": "src/components/subscription/SubscriptionForm.js",
6 "start": { 6 "start": {
7 "line": 14, 7 "line": 13,
8 "column": 21 8 "column": 21
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 17, 11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.paymentSessionError",
17 "defaultMessage": "!!!Could not initialize payment form",
18 "file": "src/components/subscription/SubscriptionForm.js",
19 "start": {
20 "line": 18,
21 "column": 23
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "subscription.type.free",
30 "defaultMessage": "!!!free",
31 "file": "src/components/subscription/SubscriptionForm.js",
32 "start": {
33 "line": 22,
34 "column": 12
35 },
36 "end": {
37 "line": 25,
38 "column": 3 12 "column": 3
39 } 13 }
40 }, 14 },
41 { 15 {
42 "id": "subscription.type.month", 16 "id": "settings.account.headlineUpgradeAccount",
43 "defaultMessage": "!!!month", 17 "defaultMessage": "!!!Upgrade your account and get the full Franz experience",
44 "file": "src/components/subscription/SubscriptionForm.js", 18 "file": "src/components/subscription/SubscriptionForm.js",
45 "start": { 19 "start": {
46 "line": 26, 20 "line": 17,
47 "column": 15 21 "column": 18
48 }, 22 },
49 "end": { 23 "end": {
50 "line": 29, 24 "line": 20,
51 "column": 3 25 "column": 3
52 } 26 }
53 }, 27 },
54 { 28 {
55 "id": "subscription.type.year", 29 "id": "subscription.teaser.intro",
56 "defaultMessage": "!!!year", 30 "defaultMessage": "!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!",
57 "file": "src/components/subscription/SubscriptionForm.js", 31 "file": "src/components/subscription/SubscriptionForm.js",
58 "start": { 32 "start": {
59 "line": 30, 33 "line": 21,
60 "column": 14 34 "column": 14
61 }, 35 },
62 "end": { 36 "end": {
63 "line": 33, 37 "line": 24,
64 "column": 3 38 "column": 3
65 } 39 }
66 }, 40 },
67 { 41 {
68 "id": "subscription.includedFeatures", 42 "id": "subscription.teaser.includedFeatures",
69 "defaultMessage": "!!!The Franz Premium Supporter Account includes", 43 "defaultMessage": "!!!Paid Franz Plans include:",
70 "file": "src/components/subscription/SubscriptionForm.js", 44 "file": "src/components/subscription/SubscriptionForm.js",
71 "start": { 45 "start": {
72 "line": 34, 46 "line": 25,
73 "column": 20 47 "column": 20
74 }, 48 },
75 "end": { 49 "end": {
76 "line": 37, 50 "line": 28,
77 "column": 3
78 }
79 },
80 {
81 "id": "subscription.features.onpremise.mattermost",
82 "defaultMessage": "!!!Add on-premise/hosted services like Mattermost",
83 "file": "src/components/subscription/SubscriptionForm.js",
84 "start": {
85 "line": 38,
86 "column": 13
87 },
88 "end": {
89 "line": 41,
90 "column": 3
91 }
92 },
93 {
94 "id": "subscription.features.noInterruptions",
95 "defaultMessage": "!!!No app delays & nagging to upgrade license",
96 "file": "src/components/subscription/SubscriptionForm.js",
97 "start": {
98 "line": 42,
99 "column": 19
100 },
101 "end": {
102 "line": 45,
103 "column": 3
104 }
105 },
106 {
107 "id": "subscription.features.proxy",
108 "defaultMessage": "!!!Proxy support for services",
109 "file": "src/components/subscription/SubscriptionForm.js",
110 "start": {
111 "line": 46,
112 "column": 9
113 },
114 "end": {
115 "line": 49,
116 "column": 3
117 }
118 },
119 {
120 "id": "subscription.features.spellchecker",
121 "defaultMessage": "!!!Support for Spellchecker",
122 "file": "src/components/subscription/SubscriptionForm.js",
123 "start": {
124 "line": 50,
125 "column": 16
126 },
127 "end": {
128 "line": 53,
129 "column": 3
130 }
131 },
132 {
133 "id": "subscription.features.workspaces",
134 "defaultMessage": "!!!Organize your services in workspaces",
135 "file": "src/components/subscription/SubscriptionForm.js",
136 "start": {
137 "line": 54,
138 "column": 14
139 },
140 "end": {
141 "line": 57,
142 "column": 3
143 }
144 },
145 {
146 "id": "subscription.features.ads",
147 "defaultMessage": "!!!No ads, ever!",
148 "file": "src/components/subscription/SubscriptionForm.js",
149 "start": {
150 "line": 58,
151 "column": 7
152 },
153 "end": {
154 "line": 61,
155 "column": 3
156 }
157 },
158 {
159 "id": "subscription.features.comingSoon",
160 "defaultMessage": "!!!coming soon",
161 "file": "src/components/subscription/SubscriptionForm.js",
162 "start": {
163 "line": 62,
164 "column": 14
165 },
166 "end": {
167 "line": 65,
168 "column": 3
169 }
170 },
171 {
172 "id": "subscription.euTaxInfo",
173 "defaultMessage": "!!!EU residents: local sales tax may apply",
174 "file": "src/components/subscription/SubscriptionForm.js",
175 "start": {
176 "line": 66,
177 "column": 13
178 },
179 "end": {
180 "line": 69,
181 "column": 3 51 "column": 3
182 } 52 }
183 } 53 }
diff --git a/src/i18n/messages/src/components/subscription/TrialForm.json b/src/i18n/messages/src/components/subscription/TrialForm.json
new file mode 100644
index 000000000..8b387ba36
--- /dev/null
+++ b/src/i18n/messages/src/components/subscription/TrialForm.json
@@ -0,0 +1,93 @@
1[
2 {
3 "id": "subscription.cta.activateTrial",
4 "defaultMessage": "!!!Yes, start the free Franz Professional trial",
5 "file": "src/components/subscription/TrialForm.js",
6 "start": {
7 "line": 14,
8 "column": 21
9 },
10 "end": {
11 "line": 17,
12 "column": 3
13 }
14 },
15 {
16 "id": "subscription.cta.allOptions",
17 "defaultMessage": "!!!See all options",
18 "file": "src/components/subscription/TrialForm.js",
19 "start": {
20 "line": 18,
21 "column": 20
22 },
23 "end": {
24 "line": 21,
25 "column": 3
26 }
27 },
28 {
29 "id": "settings.account.headlineTrialUpgrade",
30 "defaultMessage": "!!!Get the free 14 day Franz Professional Trial",
31 "file": "src/components/subscription/TrialForm.js",
32 "start": {
33 "line": 22,
34 "column": 18
35 },
36 "end": {
37 "line": 25,
38 "column": 3
39 }
40 },
41 {
42 "id": "subscription.includedProFeatures",
43 "defaultMessage": "!!!The Franz Professional Plan includes:",
44 "file": "src/components/subscription/TrialForm.js",
45 "start": {
46 "line": 26,
47 "column": 20
48 },
49 "end": {
50 "line": 29,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.trial.terms.headline",
56 "defaultMessage": "!!!No strings attached",
57 "file": "src/components/subscription/TrialForm.js",
58 "start": {
59 "line": 30,
60 "column": 29
61 },
62 "end": {
63 "line": 33,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.trial.terms.noCreditCard",
69 "defaultMessage": "!!!No credit card required",
70 "file": "src/components/subscription/TrialForm.js",
71 "start": {
72 "line": 34,
73 "column": 16
74 },
75 "end": {
76 "line": 37,
77 "column": 3
78 }
79 },
80 {
81 "id": "pricing.trial.terms.automaticTrialEnd",
82 "defaultMessage": "!!!Your free trial ends automatically after 14 days",
83 "file": "src/components/subscription/TrialForm.js",
84 "start": {
85 "line": 38,
86 "column": 21
87 },
88 "end": {
89 "line": 41,
90 "column": 3
91 }
92 }
93] \ No newline at end of file
diff --git a/src/i18n/messages/src/components/ui/ActivateTrialButton/index.json b/src/i18n/messages/src/components/ui/ActivateTrialButton/index.json
new file mode 100644
index 000000000..08c1a9293
--- /dev/null
+++ b/src/i18n/messages/src/components/ui/ActivateTrialButton/index.json
@@ -0,0 +1,54 @@
1[
2 {
3 "id": "feature.delayApp.upgrade.action",
4 "defaultMessage": "!!!Get a Franz Supporter License",
5 "file": "src/components/ui/ActivateTrialButton/index.js",
6 "start": {
7 "line": 13,
8 "column": 10
9 },
10 "end": {
11 "line": 16,
12 "column": 3
13 }
14 },
15 {
16 "id": "feature.delayApp.trial.action",
17 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
18 "file": "src/components/ui/ActivateTrialButton/index.js",
19 "start": {
20 "line": 17,
21 "column": 15
22 },
23 "end": {
24 "line": 20,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.delayApp.upgrade.actionShort",
30 "defaultMessage": "!!!Upgrade account",
31 "file": "src/components/ui/ActivateTrialButton/index.js",
32 "start": {
33 "line": 21,
34 "column": 15
35 },
36 "end": {
37 "line": 24,
38 "column": 3
39 }
40 },
41 {
42 "id": "feature.delayApp.trial.actionShort",
43 "defaultMessage": "!!!Activate the free Franz Professional trial",
44 "file": "src/components/ui/ActivateTrialButton/index.js",
45 "start": {
46 "line": 25,
47 "column": 20
48 },
49 "end": {
50 "line": 28,
51 "column": 3
52 }
53 }
54] \ 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/components/ui/PremiumFeatureContainer/index.json b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
index 320d3ca3e..0cde4cee5 100644
--- a/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
+++ b/src/i18n/messages/src/components/ui/PremiumFeatureContainer/index.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Upgrade account", 4 "defaultMessage": "!!!Upgrade account",
5 "file": "src/components/ui/PremiumFeatureContainer/index.js", 5 "file": "src/components/ui/PremiumFeatureContainer/index.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 16,
8 "column": 10 8 "column": 10
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 19,
12 "column": 3 12 "column": 3
13 } 13 }
14 } 14 }
diff --git a/src/i18n/messages/src/features/delayApp/Component.json b/src/i18n/messages/src/features/delayApp/Component.json
index bacd9444a..0d345a47b 100644
--- a/src/i18n/messages/src/features/delayApp/Component.json
+++ b/src/i18n/messages/src/features/delayApp/Component.json
@@ -4,24 +4,50 @@
4 "defaultMessage": "!!!Please purchase license to skip waiting", 4 "defaultMessage": "!!!Please purchase license to skip waiting",
5 "file": "src/features/delayApp/Component.js", 5 "file": "src/features/delayApp/Component.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 17,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 20,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
15 { 15 {
16 "id": "feature.delayApp.action", 16 "id": "feature.delayApp.trial.headline",
17 "defaultMessage": "!!!Get the free Franz Professional 14 day trial and skip the line",
18 "file": "src/features/delayApp/Component.js",
19 "start": {
20 "line": 21,
21 "column": 17
22 },
23 "end": {
24 "line": 24,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.delayApp.upgrade.action",
17 "defaultMessage": "!!!Get a Franz Supporter License", 30 "defaultMessage": "!!!Get a Franz Supporter License",
18 "file": "src/features/delayApp/Component.js", 31 "file": "src/features/delayApp/Component.js",
19 "start": { 32 "start": {
20 "line": 19, 33 "line": 25,
21 "column": 10 34 "column": 10
22 }, 35 },
23 "end": { 36 "end": {
24 "line": 22, 37 "line": 28,
38 "column": 3
39 }
40 },
41 {
42 "id": "feature.delayApp.trial.action",
43 "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional",
44 "file": "src/features/delayApp/Component.js",
45 "start": {
46 "line": 29,
47 "column": 15
48 },
49 "end": {
50 "line": 32,
25 "column": 3 51 "column": 3
26 } 52 }
27 }, 53 },
@@ -30,11 +56,11 @@
30 "defaultMessage": "!!!Franz will continue in {seconds} seconds.", 56 "defaultMessage": "!!!Franz will continue in {seconds} seconds.",
31 "file": "src/features/delayApp/Component.js", 57 "file": "src/features/delayApp/Component.js",
32 "start": { 58 "start": {
33 "line": 23, 59 "line": 33,
34 "column": 8 60 "column": 8
35 }, 61 },
36 "end": { 62 "end": {
37 "line": 26, 63 "line": 36,
38 "column": 3 64 "column": 3
39 } 65 }
40 } 66 }
diff --git a/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json b/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json
new file mode 100644
index 000000000..e6e3cef99
--- /dev/null
+++ b/src/i18n/messages/src/features/serviceLimit/components/AnnouncementScreen.json
@@ -0,0 +1,15 @@
1[
2 {
3 "id": "feature.announcements.changelog.headline",
4 "defaultMessage": "!!!Changes in Franz {version}",
5 "file": "src/features/serviceLimit/components/AnnouncementScreen.js",
6 "start": {
7 "line": 20,
8 "column": 12
9 },
10 "end": {
11 "line": 23,
12 "column": 3
13 }
14 }
15] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json b/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json
new file mode 100644
index 000000000..df5bc03e8
--- /dev/null
+++ b/src/i18n/messages/src/features/serviceLimit/components/LimitReachedInfobox.json
@@ -0,0 +1,28 @@
1[
2 {
3 "id": "feature.serviceLimit.limitReached",
4 "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.",
5 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
6 "start": {
7 "line": 11,
8 "column": 16
9 },
10 "end": {
11 "line": 14,
12 "column": 3
13 }
14 },
15 {
16 "id": "premiumFeature.button.upgradeAccount",
17 "defaultMessage": "!!!Upgrade account",
18 "file": "src/features/serviceLimit/components/LimitReachedInfobox.js",
19 "start": {
20 "line": 15,
21 "column": 10
22 },
23 "end": {
24 "line": 18,
25 "column": 3
26 }
27 }
28] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/shareFranz/Component.json b/src/i18n/messages/src/features/shareFranz/Component.json
index 34a43d5a0..72150f2b0 100644
--- a/src/i18n/messages/src/features/shareFranz/Component.json
+++ b/src/i18n/messages/src/features/shareFranz/Component.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Franz is better together!", 4 "defaultMessage": "!!!Franz is better together!",
5 "file": "src/features/shareFranz/Component.js", 5 "file": "src/features/shareFranz/Component.js",
6 "start": { 6 "start": {
7 "line": 15, 7 "line": 18,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 18, 11 "line": 21,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.", 17 "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
18 "file": "src/features/shareFranz/Component.js", 18 "file": "src/features/shareFranz/Component.js",
19 "start": { 19 "start": {
20 "line": 19, 20 "line": 22,
21 "column": 8 21 "column": 8
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 22, 24 "line": 25,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Share as email", 30 "defaultMessage": "!!!Share as email",
31 "file": "src/features/shareFranz/Component.js", 31 "file": "src/features/shareFranz/Component.js",
32 "start": { 32 "start": {
33 "line": 23, 33 "line": 26,
34 "column": 16 34 "column": 16
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 26, 37 "line": 29,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Share on Facebook", 43 "defaultMessage": "!!!Share on Facebook",
44 "file": "src/features/shareFranz/Component.js", 44 "file": "src/features/shareFranz/Component.js",
45 "start": { 45 "start": {
46 "line": 27, 46 "line": 30,
47 "column": 19 47 "column": 19
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 30, 50 "line": 33,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Share on Twitter", 56 "defaultMessage": "!!!Share on Twitter",
57 "file": "src/features/shareFranz/Component.js", 57 "file": "src/features/shareFranz/Component.js",
58 "start": { 58 "start": {
59 "line": 31, 59 "line": 34,
60 "column": 18 60 "column": 18
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 34, 63 "line": 37,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", 69 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
70 "file": "src/features/shareFranz/Component.js", 70 "file": "src/features/shareFranz/Component.js",
71 "start": { 71 "start": {
72 "line": 35, 72 "line": 38,
73 "column": 18 73 "column": 18
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 38, 76 "line": 41,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger", 82 "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger",
83 "file": "src/features/shareFranz/Component.js", 83 "file": "src/features/shareFranz/Component.js",
84 "start": { 84 "start": {
85 "line": 39, 85 "line": 42,
86 "column": 20 86 "column": 20
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 42, 89 "line": 45,
90 "column": 3 90 "column": 3
91 } 91 }
92 } 92 }
diff --git a/src/i18n/messages/src/features/todos/components/TodosWebview.json b/src/i18n/messages/src/features/todos/components/TodosWebview.json
new file mode 100644
index 000000000..2387112b4
--- /dev/null
+++ b/src/i18n/messages/src/features/todos/components/TodosWebview.json
@@ -0,0 +1,41 @@
1[
2 {
3 "id": "feature.todos.premium.info",
4 "defaultMessage": "!!!The Franz Todos Preview is currently only available for Franz Premium accounts.",
5 "file": "src/features/todos/components/TodosWebview.js",
6 "start": {
7 "line": 18,
8 "column": 15
9 },
10 "end": {
11 "line": 21,
12 "column": 3
13 }
14 },
15 {
16 "id": "feature.todos.premium.upgrade",
17 "defaultMessage": "!!!Upgrade Account",
18 "file": "src/features/todos/components/TodosWebview.js",
19 "start": {
20 "line": 22,
21 "column": 14
22 },
23 "end": {
24 "line": 25,
25 "column": 3
26 }
27 },
28 {
29 "id": "feature.todos.premium.rollout",
30 "defaultMessage": "!!!Franz Todos will be available to everyone soon.",
31 "file": "src/features/todos/components/TodosWebview.js",
32 "start": {
33 "line": 26,
34 "column": 15
35 },
36 "end": {
37 "line": 29,
38 "column": 3
39 }
40 }
41] \ No newline at end of file
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
index 9f0935620..2f340f1e9 100644
--- a/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspaceDrawer.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Workspaces", 4 "defaultMessage": "!!!Workspaces",
5 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 5 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
6 "start": { 6 "start": {
7 "line": 16, 7 "line": 17,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 19, 11 "line": 20,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!All services", 17 "defaultMessage": "!!!All services",
18 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 18 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
19 "start": { 19 "start": {
20 "line": 20, 20 "line": 21,
21 "column": 15 21 "column": 15
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 23, 24 "line": 24,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Workspaces settings", 30 "defaultMessage": "!!!Workspaces settings",
31 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 31 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
32 "start": { 32 "start": {
33 "line": 24, 33 "line": 25,
34 "column": 29 34 "column": 29
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 27, 37 "line": 28,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Info about workspace feature", 43 "defaultMessage": "!!!Info about workspace feature",
44 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 44 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
45 "start": { 45 "start": {
46 "line": 28, 46 "line": 29,
47 "column": 24 47 "column": 24
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 31, 50 "line": 32,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Create your first workspace", 56 "defaultMessage": "!!!Create your first workspace",
57 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 57 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
58 "start": { 58 "start": {
59 "line": 32, 59 "line": 33,
60 "column": 25 60 "column": 25
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 35, 63 "line": 36,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Reactivate premium account", 69 "defaultMessage": "!!!Reactivate premium account",
70 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 70 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
71 "start": { 71 "start": {
72 "line": 36, 72 "line": 37,
73 "column": 28 73 "column": 28
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 39, 76 "line": 40,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!add new workspace", 82 "defaultMessage": "!!!add new workspace",
83 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 83 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
84 "start": { 84 "start": {
85 "line": 40, 85 "line": 41,
86 "column": 24 86 "column": 24
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 43, 89 "line": 44,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Premium feature", 95 "defaultMessage": "!!!Premium feature",
96 "file": "src/features/workspaces/components/WorkspaceDrawer.js", 96 "file": "src/features/workspaces/components/WorkspaceDrawer.js",
97 "start": { 97 "start": {
98 "line": 44, 98 "line": 45,
99 "column": 23 99 "column": 23
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 47, 102 "line": 48,
103 "column": 3 103 "column": 3
104 } 104 }
105 } 105 }
diff --git a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
index ef8f1bebc..7eb4fab50 100644
--- a/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
+++ b/src/i18n/messages/src/features/workspaces/components/WorkspacesDashboard.json
@@ -4,11 +4,11 @@
4 "defaultMessage": "!!!Your workspaces", 4 "defaultMessage": "!!!Your workspaces",
5 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 5 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
6 "start": { 6 "start": {
7 "line": 17, 7 "line": 19,
8 "column": 12 8 "column": 12
9 }, 9 },
10 "end": { 10 "end": {
11 "line": 20, 11 "line": 22,
12 "column": 3 12 "column": 3
13 } 13 }
14 }, 14 },
@@ -17,11 +17,11 @@
17 "defaultMessage": "!!!You haven't added any workspaces yet.", 17 "defaultMessage": "!!!You haven't added any workspaces yet.",
18 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 18 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
19 "start": { 19 "start": {
20 "line": 21, 20 "line": 23,
21 "column": 19 21 "column": 19
22 }, 22 },
23 "end": { 23 "end": {
24 "line": 24, 24 "line": 26,
25 "column": 3 25 "column": 3
26 } 26 }
27 }, 27 },
@@ -30,11 +30,11 @@
30 "defaultMessage": "!!!Could not load your workspaces", 30 "defaultMessage": "!!!Could not load your workspaces",
31 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 31 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
32 "start": { 32 "start": {
33 "line": 25, 33 "line": 27,
34 "column": 27 34 "column": 27
35 }, 35 },
36 "end": { 36 "end": {
37 "line": 28, 37 "line": 30,
38 "column": 3 38 "column": 3
39 } 39 }
40 }, 40 },
@@ -43,11 +43,11 @@
43 "defaultMessage": "!!!Try again", 43 "defaultMessage": "!!!Try again",
44 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 44 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
45 "start": { 45 "start": {
46 "line": 29, 46 "line": 31,
47 "column": 23 47 "column": 23
48 }, 48 },
49 "end": { 49 "end": {
50 "line": 32, 50 "line": 34,
51 "column": 3 51 "column": 3
52 } 52 }
53 }, 53 },
@@ -56,11 +56,11 @@
56 "defaultMessage": "!!!Your changes have been saved", 56 "defaultMessage": "!!!Your changes have been saved",
57 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 57 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
58 "start": { 58 "start": {
59 "line": 33, 59 "line": 35,
60 "column": 15 60 "column": 15
61 }, 61 },
62 "end": { 62 "end": {
63 "line": 36, 63 "line": 38,
64 "column": 3 64 "column": 3
65 } 65 }
66 }, 66 },
@@ -69,11 +69,11 @@
69 "defaultMessage": "!!!Workspace has been deleted", 69 "defaultMessage": "!!!Workspace has been deleted",
70 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 70 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
71 "start": { 71 "start": {
72 "line": 37, 72 "line": 39,
73 "column": 15 73 "column": 15
74 }, 74 },
75 "end": { 75 "end": {
76 "line": 40, 76 "line": 42,
77 "column": 3 77 "column": 3
78 } 78 }
79 }, 79 },
@@ -82,11 +82,11 @@
82 "defaultMessage": "!!!Info about workspace feature", 82 "defaultMessage": "!!!Info about workspace feature",
83 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 83 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
84 "start": { 84 "start": {
85 "line": 41, 85 "line": 43,
86 "column": 24 86 "column": 24
87 }, 87 },
88 "end": { 88 "end": {
89 "line": 44, 89 "line": 46,
90 "column": 3 90 "column": 3
91 } 91 }
92 }, 92 },
@@ -95,11 +95,11 @@
95 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", 95 "defaultMessage": "!!!Less is More: Introducing Franz Workspaces",
96 "file": "src/features/workspaces/components/WorkspacesDashboard.js", 96 "file": "src/features/workspaces/components/WorkspacesDashboard.js",
97 "start": { 97 "start": {
98 "line": 45, 98 "line": 47,
99 "column": 28 99 "column": 28
100 }, 100 },
101 "end": { 101 "end": {
102 "line": 48, 102 "line": 50,
103 "column": 3 103 "column": 3
104 } 104 }
105 } 105 }
diff --git a/src/i18n/messages/src/helpers/plan-helpers.json b/src/i18n/messages/src/helpers/plan-helpers.json
new file mode 100644
index 000000000..df8ee19e3
--- /dev/null
+++ b/src/i18n/messages/src/helpers/plan-helpers.json
@@ -0,0 +1,54 @@
1[
2 {
3 "id": "pricing.plan.pro",
4 "defaultMessage": "!!!Franz Professional",
5 "file": "src/helpers/plan-helpers.js",
6 "start": {
7 "line": 5,
8 "column": 15
9 },
10 "end": {
11 "line": 8,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.plan.personal",
17 "defaultMessage": "!!!Franz Personal",
18 "file": "src/helpers/plan-helpers.js",
19 "start": {
20 "line": 9,
21 "column": 20
22 },
23 "end": {
24 "line": 12,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.plan.free",
30 "defaultMessage": "!!!Franz Free",
31 "file": "src/helpers/plan-helpers.js",
32 "start": {
33 "line": 13,
34 "column": 16
35 },
36 "end": {
37 "line": 16,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.plan.legacy",
43 "defaultMessage": "!!!Franz Premium",
44 "file": "src/helpers/plan-helpers.js",
45 "start": {
46 "line": 17,
47 "column": 18
48 },
49 "end": {
50 "line": 20,
51 "column": 3
52 }
53 }
54] \ No newline at end of file
diff --git a/src/i18n/messages/src/helpers/pricing-helpers.json b/src/i18n/messages/src/helpers/pricing-helpers.json
new file mode 100644
index 000000000..4030a3e3b
--- /dev/null
+++ b/src/i18n/messages/src/helpers/pricing-helpers.json
@@ -0,0 +1,80 @@
1[
2 {
3 "id": "pricing.plan.pro-yearly",
4 "defaultMessage": "!!!Franz Professional Yearly",
5 "file": "src/helpers/pricing-helpers.js",
6 "start": {
7 "line": 5,
8 "column": 22
9 },
10 "end": {
11 "line": 8,
12 "column": 3
13 }
14 },
15 {
16 "id": "pricing.plan.pro-monthly",
17 "defaultMessage": "!!!Franz Professional Monthly",
18 "file": "src/helpers/pricing-helpers.js",
19 "start": {
20 "line": 9,
21 "column": 23
22 },
23 "end": {
24 "line": 12,
25 "column": 3
26 }
27 },
28 {
29 "id": "pricing.plan.personal-yearly",
30 "defaultMessage": "!!!Franz Personal Yearly",
31 "file": "src/helpers/pricing-helpers.js",
32 "start": {
33 "line": 13,
34 "column": 27
35 },
36 "end": {
37 "line": 16,
38 "column": 3
39 }
40 },
41 {
42 "id": "pricing.plan.personal-monthly",
43 "defaultMessage": "!!!Franz Personal Monthly",
44 "file": "src/helpers/pricing-helpers.js",
45 "start": {
46 "line": 17,
47 "column": 28
48 },
49 "end": {
50 "line": 20,
51 "column": 3
52 }
53 },
54 {
55 "id": "pricing.plan.free",
56 "defaultMessage": "!!!Franz Free",
57 "file": "src/helpers/pricing-helpers.js",
58 "start": {
59 "line": 21,
60 "column": 16
61 },
62 "end": {
63 "line": 24,
64 "column": 3
65 }
66 },
67 {
68 "id": "pricing.plan.legacy",
69 "defaultMessage": "!!!Franz Premium",
70 "file": "src/helpers/pricing-helpers.js",
71 "start": {
72 "line": 25,
73 "column": 18
74 },
75 "end": {
76 "line": 28,
77 "column": 3
78 }
79 }
80] \ No newline at end of file
diff --git a/src/index.js b/src/index.js
index 55592c328..d9d51fd5b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -105,35 +105,6 @@ if (!gotTheLock) {
105 } 105 }
106 }); 106 });
107} 107}
108// const isSecondInstance = app.makeSingleInstance((argv) => {
109// if (mainWindow) {
110// if (mainWindow.isMinimized()) mainWindow.restore();
111// mainWindow.focus();
112
113// if (process.platform === 'win32') {
114// // Keep only command line / deep linked arguments
115// const url = argv.slice(1);
116
117// if (url) {
118// handleDeepLink(mainWindow, url.toString());
119// }
120// }
121// }
122
123// if (argv.includes('--reset-window')) {
124// // Needs to be delayed to not interfere with mainWindow.restore();
125// setTimeout(() => {
126// debug('Resetting windows via Task');
127// mainWindow.setPosition(DEFAULT_WINDOW_OPTIONS.x + 100, DEFAULT_WINDOW_OPTIONS.y + 100);
128// mainWindow.setSize(DEFAULT_WINDOW_OPTIONS.width, DEFAULT_WINDOW_OPTIONS.height);
129// }, 1);
130// }
131// });
132
133// if (isSecondInstance) {
134// console.log('An instance of Franz is already running. Exiting...');
135// app.exit();
136// }
137 108
138// Fix Unity indicator issue 109// Fix Unity indicator issue
139// https://github.com/electron/electron/issues/9046 110// https://github.com/electron/electron/issues/9046
diff --git a/src/models/Service.js b/src/models/Service.js
index 88bce3360..848a84aa2 100644
--- a/src/models/Service.js
+++ b/src/models/Service.js
@@ -4,6 +4,11 @@ import normalizeUrl from 'normalize-url';
4 4
5const debug = require('debug')('Franz:Service'); 5const debug = require('debug')('Franz:Service');
6 6
7export const RESTRICTION_TYPES = {
8 SERVICE_LIMIT: 0,
9 CUSTOM_URL: 1,
10};
11
7export default class Service { 12export default class Service {
8 id = ''; 13 id = '';
9 14
@@ -59,6 +64,12 @@ export default class Service {
59 64
60 @observable errorMessage = ''; 65 @observable errorMessage = '';
61 66
67 @observable isUsingCustomUrl = false;
68
69 @observable isServiceAccessRestricted = false;
70
71 @observable restrictionType = null;
72
62 constructor(data, recipe) { 73 constructor(data, recipe) {
63 if (!data) { 74 if (!data) {
64 console.error('Service config not valid'); 75 console.error('Service config not valid');
@@ -111,6 +122,10 @@ export default class Service {
111 this.unreadDirectMessageCount = 0; 122 this.unreadDirectMessageCount = 0;
112 this.unreadIndirectMessageCount = 0; 123 this.unreadIndirectMessageCount = 0;
113 } 124 }
125
126 if (this.recipe.hasCustomUrl && this.customUrl) {
127 this.isUsingCustomUrl = true;
128 }
114 }); 129 });
115 } 130 }
116 131
diff --git a/src/models/User.js b/src/models/User.js
index bec78fc16..0a2b1f62a 100644
--- a/src/models/User.js
+++ b/src/models/User.js
@@ -20,6 +20,10 @@ export default class User {
20 20
21 @observable isSubscriptionOwner = false; 21 @observable isSubscriptionOwner = false;
22 22
23 @observable hasSubscription = false;
24
25 @observable hadSubscription = false;
26
23 @observable isPremium = false; 27 @observable isPremium = false;
24 28
25 @observable beta = false; 29 @observable beta = false;
@@ -32,6 +36,9 @@ export default class User {
32 36
33 @observable locale = false; 37 @observable locale = false;
34 38
39 @observable team = {};
40
41
35 constructor(data) { 42 constructor(data) {
36 if (!data.id) { 43 if (!data.id) {
37 throw Error('User requires Id'); 44 throw Error('User requires Id');
@@ -47,8 +54,13 @@ export default class User {
47 this.beta = data.beta || this.beta; 54 this.beta = data.beta || this.beta;
48 this.donor = data.donor || this.donor; 55 this.donor = data.donor || this.donor;
49 this.isDonor = data.isDonor || this.isDonor; 56 this.isDonor = data.isDonor || this.isDonor;
50 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
51 this.isMiner = data.isMiner || this.isMiner; 57 this.isMiner = data.isMiner || this.isMiner;
52 this.locale = data.locale || this.locale; 58 this.locale = data.locale || this.locale;
59
60 this.isSubscriptionOwner = data.isSubscriptionOwner || this.isSubscriptionOwner;
61 this.hasSubscription = data.hasSubscription || this.hasSubscription;
62 this.hadSubscription = data.hadSubscription || this.hadSubscription;
63
64 this.team = data.team || this.team;
53 } 65 }
54} 66}
diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js
index 2ac306a2a..6054e6721 100644
--- a/src/stores/AppStore.js
+++ b/src/stores/AppStore.js
@@ -357,6 +357,8 @@ export default class AppStore extends Store {
357 this.locale = this._getDefaultLocale(); 357 this.locale = this._getDefaultLocale();
358 } 358 }
359 359
360 moment.locale(this.locale);
361
360 debug(`Set locale to "${this.locale}"`); 362 debug(`Set locale to "${this.locale}"`);
361 } 363 }
362 364
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 35a050c67..cf28b6bec 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -16,6 +16,8 @@ import workspaces from '../features/workspaces';
16import shareFranz from '../features/shareFranz'; 16import shareFranz from '../features/shareFranz';
17import announcements from '../features/announcements'; 17import announcements from '../features/announcements';
18import settingsWS from '../features/settingsWS'; 18import settingsWS from '../features/settingsWS';
19import serviceLimit from '../features/serviceLimit';
20import communityRecipes from '../features/communityRecipes';
19import todos from '../features/todos'; 21import todos from '../features/todos';
20 22
21import { DEFAULT_FEATURES_CONFIG } from '../config'; 23import { DEFAULT_FEATURES_CONFIG } from '../config';
@@ -76,6 +78,8 @@ export default class FeaturesStore extends Store {
76 shareFranz(this.stores, this.actions); 78 shareFranz(this.stores, this.actions);
77 announcements(this.stores, this.actions); 79 announcements(this.stores, this.actions);
78 settingsWS(this.stores, this.actions); 80 settingsWS(this.stores, this.actions);
81 serviceLimit(this.stores, this.actions);
82 communityRecipes(this.stores, this.actions);
79 todos(this.stores, this.actions); 83 todos(this.stores, this.actions);
80 } 84 }
81} 85}
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js
index b13425249..2fc543192 100644
--- a/src/stores/ServicesStore.js
+++ b/src/stores/ServicesStore.js
@@ -13,6 +13,8 @@ import CachedRequest from './lib/CachedRequest';
13import { matchRoute } from '../helpers/routing-helpers'; 13import { matchRoute } from '../helpers/routing-helpers';
14import { gaEvent, statsEvent } from '../lib/analytics'; 14import { gaEvent, statsEvent } from '../lib/analytics';
15import { workspaceStore } from '../features/workspaces'; 15import { workspaceStore } from '../features/workspaces';
16import { serviceLimitStore } from '../features/serviceLimit';
17import { RESTRICTION_TYPES } from '../models/Service';
16 18
17const debug = require('debug')('Franz:ServiceStore'); 19const debug = require('debug')('Franz:ServiceStore');
18 20
@@ -75,6 +77,7 @@ export default class ServicesStore extends Store {
75 this._saveActiveService.bind(this), 77 this._saveActiveService.bind(this),
76 this._logoutReaction.bind(this), 78 this._logoutReaction.bind(this),
77 this._handleMuteSettings.bind(this), 79 this._handleMuteSettings.bind(this),
80 this._restrictServiceAccess.bind(this),
78 ]); 81 ]);
79 82
80 // Just bind this 83 // Just bind this
@@ -98,7 +101,10 @@ export default class ServicesStore extends Store {
98 if (this.stores.user.isLoggedIn) { 101 if (this.stores.user.isLoggedIn) {
99 const services = this.allServicesRequest.execute().result; 102 const services = this.allServicesRequest.execute().result;
100 if (services) { 103 if (services) {
101 return observable(services.slice().slice().sort((a, b) => a.order - b.order)); 104 return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => {
105 s.index = index;
106 return s;
107 }));
102 } 108 }
103 } 109 }
104 return []; 110 return [];
@@ -153,6 +159,8 @@ export default class ServicesStore extends Store {
153 159
154 // Actions 160 // Actions
155 @action async _createService({ recipeId, serviceData, redirect = true }) { 161 @action async _createService({ recipeId, serviceData, redirect = true }) {
162 if (serviceLimitStore.userHasReachedServiceLimit) return;
163
156 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData); 164 const data = this._cleanUpTeamIdAndCustomUrl(recipeId, serviceData);
157 165
158 const response = await this.createServiceRequest.execute(recipeId, data)._promise; 166 const response = await this.createServiceRequest.execute(recipeId, data)._promise;
@@ -681,6 +689,35 @@ export default class ServicesStore extends Store {
681 return serviceData; 689 return serviceData;
682 } 690 }
683 691
692 _restrictServiceAccess() {
693 const { features } = this.stores.features;
694 const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit;
695
696 this.all.map((service, index) => {
697 if (userHasReachedServiceLimit) {
698 service.isServiceAccessRestricted = index >= serviceLimit;
699
700 if (service.isServiceAccessRestricted) {
701 service.restrictionType = RESTRICTION_TYPES.SERVICE_LIMIT;
702
703 debug('Restricting access to server due to service limit');
704 }
705 }
706
707 if (service.isUsingCustomUrl) {
708 service.isServiceAccessRestricted = !features.isCustomUrlIncludedInCurrentPlan;
709
710 if (service.isServiceAccessRestricted) {
711 service.restrictionType = RESTRICTION_TYPES.CUSTOM_URL;
712
713 debug('Restricting access to server due to custom url');
714 }
715 }
716
717 return service;
718 });
719 }
720
684 // Helper 721 // Helper
685 _initializeServiceRecipeInWebview(serviceId) { 722 _initializeServiceRecipeInWebview(serviceId) {
686 const service = this.one(serviceId); 723 const service = this.one(serviceId);
diff --git a/src/stores/UIStore.js b/src/stores/UIStore.js
index 9680c5bcc..2c785111f 100644
--- a/src/stores/UIStore.js
+++ b/src/stores/UIStore.js
@@ -46,6 +46,7 @@ export default class UIStore extends Store {
46 // Actions 46 // Actions
47 @action _openSettings({ path = '/settings' }) { 47 @action _openSettings({ path = '/settings' }) {
48 const settingsPath = path !== '/settings' ? `/settings/${path}` : path; 48 const settingsPath = path !== '/settings' ? `/settings/${path}` : path;
49 console.log(settingsPath);
49 this.stores.router.push(settingsPath); 50 this.stores.router.push(settingsPath);
50 } 51 }
51 52
diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js
index b5423af3b..7ac7d2375 100644
--- a/src/stores/UserStore.js
+++ b/src/stores/UserStore.js
@@ -2,12 +2,16 @@ 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';
13import { getPlan } from '../helpers/plan-helpers';
14import { PLANS } from '../config';
11 15
12const debug = require('debug')('Franz:UserStore'); 16const debug = require('debug')('Franz:UserStore');
13 17
@@ -37,6 +41,8 @@ export default class UserStore extends Store {
37 41
38 @observable passwordRequest = new Request(this.api.user, 'password'); 42 @observable passwordRequest = new Request(this.api.user, 'password');
39 43
44 @observable activateTrialRequest = new Request(this.api.user, 'activateTrial');
45
40 @observable inviteRequest = new Request(this.api.user, 'invite'); 46 @observable inviteRequest = new Request(this.api.user, 'invite');
41 47
42 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo'); 48 @observable getUserInfoRequest = new CachedRequest(this.api.user, 'getInfo');
@@ -57,7 +63,9 @@ export default class UserStore extends Store {
57 63
58 @observable accountType; 64 @observable accountType;
59 65
60 @observable hasCompletedSignup = null; 66 @observable hasCompletedSignup = false;
67
68 @observable hasActivatedTrial = false;
61 69
62 @observable userData = {}; 70 @observable userData = {};
63 71
@@ -77,6 +85,7 @@ export default class UserStore extends Store {
77 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this)); 85 this.actions.user.retrievePassword.listen(this._retrievePassword.bind(this));
78 this.actions.user.logout.listen(this._logout.bind(this)); 86 this.actions.user.logout.listen(this._logout.bind(this));
79 this.actions.user.signup.listen(this._signup.bind(this)); 87 this.actions.user.signup.listen(this._signup.bind(this));
88 this.actions.user.activateTrial.listen(this._activateTrial.bind(this));
80 this.actions.user.invite.listen(this._invite.bind(this)); 89 this.actions.user.invite.listen(this._invite.bind(this));
81 this.actions.user.update.listen(this._update.bind(this)); 90 this.actions.user.update.listen(this._update.bind(this));
82 this.actions.user.resetStatus.listen(this._resetStatus.bind(this)); 91 this.actions.user.resetStatus.listen(this._resetStatus.bind(this));
@@ -87,6 +96,7 @@ export default class UserStore extends Store {
87 this.registerReactions([ 96 this.registerReactions([
88 this._requireAuthenticatedUser, 97 this._requireAuthenticatedUser,
89 this._getUserData.bind(this), 98 this._getUserData.bind(this),
99 this._resetTrialActivationState.bind(this),
90 ]); 100 ]);
91 } 101 }
92 102
@@ -142,10 +152,30 @@ export default class UserStore extends Store {
142 return this.getUserInfoRequest.execute().result || {}; 152 return this.getUserInfoRequest.execute().result || {};
143 } 153 }
144 154
155 @computed get team() {
156 return this.data.team || null;
157 }
158
145 @computed get isPremium() { 159 @computed get isPremium() {
146 return !!this.data.isPremium; 160 return !!this.data.isPremium;
147 } 161 }
148 162
163 @computed get isPersonal() {
164 if (!this.team.plan) return false;
165 const plan = getPlan(this.team.plan);
166
167 return plan === PLANS.PERSONAL;
168 }
169
170 @computed get isPro() {
171 if (!this.team.plan && this.isPremium) return true;
172
173 if (!this.team.plan) return false;
174 const plan = getPlan(this.team.plan);
175
176 return plan === PLANS.PRO;
177 }
178
149 @computed get legacyServices() { 179 @computed get legacyServices() {
150 return this.getLegacyServicesRequest.execute() || {}; 180 return this.getLegacyServicesRequest.execute() || {};
151 } 181 }
@@ -199,6 +229,24 @@ export default class UserStore extends Store {
199 gaEvent('User', 'retrievePassword'); 229 gaEvent('User', 'retrievePassword');
200 } 230 }
201 231
232 @action async _activateTrial({ planId }) {
233 debug('activate trial', planId);
234
235 this.activateTrialRequest.execute({
236 plan: planId,
237 });
238
239 await this.activateTrialRequest._promise;
240
241 this.hasActivatedTrial = true;
242
243 this.stores.features.featuresRequest.invalidate({ immediately: true });
244 this.stores.user.getUserInfoRequest.invalidate({ immediately: true });
245
246
247 gaEvent('User', 'activateTrial');
248 }
249
202 @action async _invite({ invites }) { 250 @action async _invite({ invites }) {
203 const data = invites.filter(invite => invite.email !== ''); 251 const data = invites.filter(invite => invite.email !== '');
204 252
@@ -318,6 +366,14 @@ export default class UserStore extends Store {
318 } 366 }
319 } 367 }
320 368
369 async _resetTrialActivationState() {
370 if (this.hasActivatedTrial) {
371 await sleep(ms('12s'));
372
373 this.hasActivatedTrial = false;
374 }
375 }
376
321 // Helpers 377 // Helpers
322 _parseToken(authToken) { 378 _parseToken(authToken) {
323 try { 379 try {
@@ -347,6 +403,15 @@ export default class UserStore extends Store {
347 } 403 }
348 } 404 }
349 405
406 getAuthURL(url) {
407 const parsedUrl = new URL(url);
408 const params = new URLSearchParams(parsedUrl.search.slice(1));
409
410 params.append('authToken', this.authToken);
411
412 return `${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`;
413 }
414
350 async _migrateUserLocale() { 415 async _migrateUserLocale() {
351 await this.getUserInfoRequest._promise; 416 await this.getUserInfoRequest._promise;
352 417
diff --git a/src/stores/index.js b/src/stores/index.js
index e9ef523f8..10dd56665 100644
--- a/src/stores/index.js
+++ b/src/stores/index.js
@@ -12,6 +12,8 @@ import RequestStore from './RequestStore';
12import GlobalErrorStore from './GlobalErrorStore'; 12import GlobalErrorStore from './GlobalErrorStore';
13import { workspaceStore } from '../features/workspaces'; 13import { workspaceStore } from '../features/workspaces';
14import { announcementsStore } from '../features/announcements'; 14import { announcementsStore } from '../features/announcements';
15import { serviceLimitStore } from '../features/serviceLimit';
16import { communityRecipesStore } from '../features/communityRecipes';
15import { todosStore } from '../features/todos'; 17import { todosStore } from '../features/todos';
16 18
17export default (api, actions, router) => { 19export default (api, actions, router) => {
@@ -32,6 +34,8 @@ export default (api, actions, router) => {
32 globalError: new GlobalErrorStore(stores, api, actions), 34 globalError: new GlobalErrorStore(stores, api, actions),
33 workspaces: workspaceStore, 35 workspaces: workspaceStore,
34 announcements: announcementsStore, 36 announcements: announcementsStore,
37 serviceLimit: serviceLimitStore,
38 communityRecipes: communityRecipesStore,
35 todos: todosStore, 39 todos: todosStore,
36 }); 40 });
37 // Initialize all stores 41 // Initialize all stores
diff --git a/src/styles/auth.scss b/src/styles/auth.scss
index 0a075036a..154a71a36 100644
--- a/src/styles/auth.scss
+++ b/src/styles/auth.scss
@@ -9,7 +9,7 @@
9 } 9 }
10 10
11 .auth__logo.auth__logo--sm { 11 .auth__logo.auth__logo--sm {
12 border: 4px solid $dark-theme-black; 12 border: none;
13 box-shadow: 0 0 6px rgba($dark-theme-black, .5); 13 box-shadow: 0 0 6px rgba($dark-theme-black, .5);
14 } 14 }
15 15
diff --git a/src/styles/recipes.scss b/src/styles/recipes.scss
index 84222e1fe..5bdc60a57 100644
--- a/src/styles/recipes.scss
+++ b/src/styles/recipes.scss
@@ -2,6 +2,7 @@
2 2
3.theme__dark .recipe-teaser { 3.theme__dark .recipe-teaser {
4 background-color: $dark-theme-gray-dark; 4 background-color: $dark-theme-gray-dark;
5 color: $dark-theme-text-color;
5 6
6 &:hover { background-color: $dark-theme-gray; } 7 &:hover { background-color: $dark-theme-gray; }
7} 8}
@@ -12,7 +13,7 @@
12 display: flex; 13 display: flex;
13 flex-flow: row wrap; 14 flex-flow: row wrap;
14 height: auto; 15 height: auto;
15 min-height: 70%; 16 // min-height: 70%;
16 17
17 &.recipes__list--disabled { 18 &.recipes__list--disabled {
18 filter: grayscale(100%); 19 filter: grayscale(100%);
diff --git a/src/styles/reset.scss b/src/styles/reset.scss
index f46ede4a2..d87ce652a 100644
--- a/src/styles/reset.scss
+++ b/src/styles/reset.scss
@@ -51,7 +51,6 @@ button {
51 padding: 0; 51 padding: 0;
52 52
53 &:focus { outline: 0; } 53 &:focus { outline: 0; }
54 .theme__dark & { color: $dark-theme-gray-smoke; }
55} 54}
56 55
57html { 56html {
diff --git a/src/styles/settings.scss b/src/styles/settings.scss
index 1baff8b54..0955aaa0c 100644
--- a/src/styles/settings.scss
+++ b/src/styles/settings.scss
@@ -360,6 +360,7 @@
360 .account__subscription-button { margin-left: auto; } 360 .account__subscription-button { margin-left: auto; }
361 .franz-form__button { white-space: nowrap; } 361 .franz-form__button { white-space: nowrap; }
362 div { height: auto; } 362 div { height: auto; }
363 [data-type="franz-button"] div { height: 100% }
363 364
364 .invoices { 365 .invoices {
365 width: 100%; 366 width: 100%;
diff --git a/uidev/src/stories/button.stories.tsx b/uidev/src/stories/button.stories.tsx
index f7537895c..5c1c9246d 100644
--- a/uidev/src/stories/button.stories.tsx
+++ b/uidev/src/stories/button.stories.tsx
@@ -1,10 +1,10 @@
1import { mdiInformation } from '@mdi/js';
1import { observable } from 'mobx'; 2import { observable } from 'mobx';
2import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
3import React from 'react'; 4import React from 'react';
4import injectSheet from 'react-jss'; 5import injectSheet from 'react-jss';
5 6
6import { Button, Input } from '@meetfranz/forms'; 7import { Button, Input } from '@meetfranz/forms';
7import { classes } from 'istanbul-lib-coverage';
8import { Classes } from 'jss'; 8import { Classes } from 'jss';
9import { storiesOf } from '../stores/stories'; 9import { storiesOf } from '../stores/stories';
10 10
@@ -92,7 +92,7 @@ storiesOf('Button')
92 )) 92 ))
93 .add('With icon', () => ( 93 .add('With icon', () => (
94 <WithStoreButton store={createStore({ 94 <WithStoreButton store={createStore({
95 icon: 'mdiInformation', 95 icon: mdiInformation,
96 })} /> 96 })} />
97 )) 97 ))
98 .add('As link', () => ( 98 .add('As link', () => (
@@ -131,7 +131,7 @@ storiesOf('Button')
131 <div className={classes.combinedElements}> 131 <div className={classes.combinedElements}>
132 <Input showLabel={false} className={classes.input} noMargin /> 132 <Input showLabel={false} className={classes.input} noMargin />
133 <WithStoreButton store={createStore({ 133 <WithStoreButton store={createStore({
134 icon: 'mdiInformation', 134 icon: mdiInformation,
135 })} /> 135 })} />
136 </div> 136 </div>
137 )), 137 )),
diff --git a/uidev/src/stories/icon.stories.tsx b/uidev/src/stories/icon.stories.tsx
index c8e7f8ced..f9aa1635b 100644
--- a/uidev/src/stories/icon.stories.tsx
+++ b/uidev/src/stories/icon.stories.tsx
@@ -1,53 +1,14 @@
1import { observable } from 'mobx'; 1import { mdiAccountCircle } from '@mdi/js';
2import { observer } from 'mobx-react';
3import React from 'react'; 2import React from 'react';
4import uuid from 'uuid/v4';
5 3
6import { Icon } from '@meetfranz/ui'; 4import { Icon } from '@meetfranz/ui';
7import { storiesOf } from '../stores/stories'; 5import { storiesOf } from '../stores/stories';
8 6
9// interface IStoreArgs {
10// value?: boolean;
11// checked?: boolean;
12// label?: string;
13// id?: string;
14// name?: string;
15// disabled?: boolean;
16// error?: string;
17// }
18
19// const createStore = (args?: IStoreArgs) => {
20// return observable(Object.assign({
21// id: `element-${uuid()}`,
22// name: 'toggle',
23// label: 'Label',
24// value: true,
25// checked: false,
26// disabled: false,
27// error: '',
28// }, args));
29// };
30
31// const WithStoreToggle = observer(({ store }: { store: any }) => (
32// <>
33// <Toggle
34// value={store.value}
35// checked={store.checked}
36// label={store.label}
37// id={store.id}
38// name={store.name}
39// disabled={store.disabled}
40// error={store.error}
41// onChange={() => store.checked = !store.checked}
42// />
43// </>
44// ));
45
46storiesOf('Icon') 7storiesOf('Icon')
47 .add('Basic', () => ( 8 .add('Basic', () => (
48 <> 9 <>
49 <Icon icon="mdiAccountCircle" /> 10 <Icon icon={mdiAccountCircle} />
50 <Icon icon="mdiAccountCircle" size={2} /> 11 <Icon icon={mdiAccountCircle} size={2} />
51 <Icon icon="mdiAccountCircle" size={3} /> 12 <Icon icon={mdiAccountCircle} size={3} />
52 </> 13 </>
53 )); 14 ));
diff --git a/uidev/src/stories/infobox.stories.tsx b/uidev/src/stories/infobox.stories.tsx
index 144855376..c3442da0d 100644
--- a/uidev/src/stories/infobox.stories.tsx
+++ b/uidev/src/stories/infobox.stories.tsx
@@ -1,3 +1,4 @@
1import { mdiEarth } from '@mdi/js';
1import { observable } from 'mobx'; 2import { observable } from 'mobx';
2import { observer } from 'mobx-react'; 3import { observer } from 'mobx-react';
3import React from 'react'; 4import React from 'react';
@@ -44,7 +45,7 @@ storiesOf('Infobox')
44 .add('Icon + Dismissable', () => ( 45 .add('Icon + Dismissable', () => (
45 <WithStoreInfobox 46 <WithStoreInfobox
46 store={createStore({ 47 store={createStore({
47 icon: 'mdiEarth', 48 icon: mdiEarth,
48 dismissable: true, 49 dismissable: true,
49 })} 50 })}
50 > 51 >
@@ -54,7 +55,7 @@ storiesOf('Infobox')
54 .add('With CTA', () => ( 55 .add('With CTA', () => (
55 <WithStoreInfobox 56 <WithStoreInfobox
56 store={createStore({ 57 store={createStore({
57 icon: 'mdiEarth', 58 icon: mdiEarth,
58 ctaLabel: 'Ok, hi!', 59 ctaLabel: 'Ok, hi!',
59 })} 60 })}
60 > 61 >
@@ -64,7 +65,7 @@ storiesOf('Infobox')
64 .add('With long text', () => ( 65 .add('With long text', () => (
65 <WithStoreInfobox 66 <WithStoreInfobox
66 store={createStore({ 67 store={createStore({
67 icon: 'mdiEarth', 68 icon: mdiEarth,
68 ctaLabel: 'Ok, hi!', 69 ctaLabel: 'Ok, hi!',
69 })} 70 })}
70 > 71 >
@@ -74,7 +75,7 @@ storiesOf('Infobox')
74 .add('Secondary', () => ( 75 .add('Secondary', () => (
75 <WithStoreInfobox 76 <WithStoreInfobox
76 store={createStore({ 77 store={createStore({
77 icon: 'mdiEarth', 78 icon: mdiEarth,
78 ctaLabel: 'Ok, hi!', 79 ctaLabel: 'Ok, hi!',
79 type: 'secondary', 80 type: 'secondary',
80 })} 81 })}
@@ -85,7 +86,7 @@ storiesOf('Infobox')
85 .add('Success', () => ( 86 .add('Success', () => (
86 <WithStoreInfobox 87 <WithStoreInfobox
87 store={createStore({ 88 store={createStore({
88 icon: 'mdiEarth', 89 icon: mdiEarth,
89 ctaLabel: 'Ok, hi!', 90 ctaLabel: 'Ok, hi!',
90 type: 'success', 91 type: 'success',
91 })} 92 })}
@@ -96,7 +97,7 @@ storiesOf('Infobox')
96 .add('Warning', () => ( 97 .add('Warning', () => (
97 <WithStoreInfobox 98 <WithStoreInfobox
98 store={createStore({ 99 store={createStore({
99 icon: 'mdiEarth', 100 icon: mdiEarth,
100 ctaLabel: 'Ok, hi!', 101 ctaLabel: 'Ok, hi!',
101 type: 'warning', 102 type: 'warning',
102 })} 103 })}
@@ -107,7 +108,7 @@ storiesOf('Infobox')
107 .add('Danger', () => ( 108 .add('Danger', () => (
108 <WithStoreInfobox 109 <WithStoreInfobox
109 store={createStore({ 110 store={createStore({
110 icon: 'mdiEarth', 111 icon: mdiEarth,
111 ctaLabel: 'Ok, hi!', 112 ctaLabel: 'Ok, hi!',
112 type: 'danger', 113 type: 'danger',
113 })} 114 })}
@@ -118,7 +119,7 @@ storiesOf('Infobox')
118 .add('Inverted', () => ( 119 .add('Inverted', () => (
119 <WithStoreInfobox 120 <WithStoreInfobox
120 store={createStore({ 121 store={createStore({
121 icon: 'mdiEarth', 122 icon: mdiEarth,
122 ctaLabel: 'Ok, hi!', 123 ctaLabel: 'Ok, hi!',
123 type: 'inverted', 124 type: 'inverted',
124 })} 125 })}