From 1bae1dfcbc4a5f590c51103635006198ae6a91d6 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Tue, 30 Apr 2019 15:23:38 +0200 Subject: Enforce service limit --- src/components/settings/recipes/RecipesDashboard.js | 2 ++ src/components/settings/services/EditServiceForm.js | 5 ++++- src/components/settings/services/ServicesDashboard.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'src/components') diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js index 00cd725cf..862bd4a27 100644 --- a/src/components/settings/recipes/RecipesDashboard.js +++ b/src/components/settings/recipes/RecipesDashboard.js @@ -10,6 +10,7 @@ import RecipeItem from './RecipeItem'; import Loader from '../../ui/Loader'; import Appear from '../../ui/effects/Appear'; import { FRANZ_SERVICE_REQUEST } from '../../../config'; +import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; const messages = defineMessages({ headline: { @@ -86,6 +87,7 @@ export default @observer class RecipesDashboard extends Component {

{intl.formatMessage(messages.headline)}

+
{serviceStatus.length > 0 && serviceStatus.includes('created') && ( diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 4ba2eb844..c089a1582 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -17,6 +17,8 @@ import ImageUpload from '../../ui/ImageUpload'; import Select from '../../ui/Select'; import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; +import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; +import { serviceLimitStore } from '../../../features/serviceLimit'; const messages = defineMessages({ saveService: { @@ -252,6 +254,7 @@ export default @observer class EditServiceForm extends Component { )}
+
this.submit(e)} id="form">
@@ -418,7 +421,7 @@ export default @observer class EditServiceForm extends Component { type="submit" label={intl.formatMessage(messages.saveService)} htmlForm="form" - disabled={action !== 'edit' && form.isPristine && requiresUserInput} + disabled={action !== 'edit' && ((form.isPristine && requiresUserInput) || serviceLimitStore.userHasReachedServiceLimit)} /> )}
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'; import Loader from '../../ui/Loader'; import ServiceItem from './ServiceItem'; import Appear from '../../ui/effects/Appear'; +import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox'; const messages = defineMessages({ headline: { @@ -91,6 +92,7 @@ export default @observer class ServicesDashboard extends Component {

{intl.formatMessage(messages.headline)}

+
{!isLoading && ( Date: Thu, 2 May 2019 11:27:23 +0200 Subject: Add custom recipe limitation --- packages/ui/src/badge/ProBadge.tsx | 5 +- src/components/settings/recipes/RecipeItem.js | 2 +- .../settings/recipes/RecipesDashboard.js | 194 +++++++++++++++++---- src/config.js | 1 + src/containers/settings/RecipesScreen.js | 40 ++++- src/features/communityRecipes/index.js | 28 +++ src/features/communityRecipes/store.js | 31 ++++ src/i18n/locales/defaultMessages.json | 178 +++++++++++++++++-- src/i18n/locales/en-US.json | 8 +- .../settings/recipes/RecipesDashboard.json | 116 ++++++++++-- src/stores/FeaturesStore.js | 2 + src/stores/index.js | 2 + src/styles/recipes.scss | 2 +- 13 files changed, 526 insertions(+), 83 deletions(-) create mode 100644 src/features/communityRecipes/index.js create mode 100644 src/features/communityRecipes/store.js (limited to 'src/components') diff --git a/packages/ui/src/badge/ProBadge.tsx b/packages/ui/src/badge/ProBadge.tsx index 612e23210..2dad7ef49 100644 --- a/packages/ui/src/badge/ProBadge.tsx +++ b/packages/ui/src/badge/ProBadge.tsx @@ -3,13 +3,14 @@ import classnames from 'classnames'; import React, { Component } from 'react'; import injectStyle from 'react-jss'; -import { Icon, Badge } from '../'; +import { Badge, Icon } from '../'; import { IWithStyle } from '../typings/generic'; interface IProps extends IWithStyle { badgeClasses?: string; iconClasses?: string; inverted?: boolean; + className?: string; } const styles = (theme: Theme) => ({ @@ -37,6 +38,7 @@ class ProBadgeComponent extends Component { badgeClasses, iconClasses, inverted, + className, } = this.props; return ( @@ -46,6 +48,7 @@ class ProBadgeComponent extends Component { classes.badge, inverted && classes.invertedBadge, badgeClasses, + className, ])} > - {recipe.local && ( + {recipe.isDevRecipe && ( dev )} div': { + fontFamily: 'SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace', + }, + }, + actionContainer: { + '& button': { + margin: [0, 10], + }, + }, + devRecipeList: { + marginTop: 20, + height: 'auto', + }, + proBadge: { + marginLeft: '10px !important', + }, +}; + +export default @injectSheet(styles) @observer class RecipesDashboard extends Component { static propTypes = { recipes: MobxPropTypes.arrayOrObservableArray.isRequired, isLoading: PropTypes.bool.isRequired, @@ -55,12 +111,19 @@ export default @observer class RecipesDashboard extends Component { searchRecipes: PropTypes.func.isRequired, resetSearch: PropTypes.func.isRequired, serviceStatus: MobxPropTypes.arrayOrObservableArray.isRequired, - devRecipesCount: PropTypes.number.isRequired, searchNeedle: PropTypes.string, + recipeFilter: PropTypes.string, + recipeDirectory: PropTypes.string.isRequired, + openRecipeDirectory: PropTypes.func.isRequired, + openDevDocs: PropTypes.func.isRequired, + classes: PropTypes.object.isRequired, + isCommunityRecipesPremiumFeature: PropTypes.bool.isRequired, + isUserPremiumUser: PropTypes.bool.isRequired, }; static defaultProps = { searchNeedle: '', + recipeFilter: 'all', } static contextTypes = { @@ -76,11 +139,21 @@ export default @observer class RecipesDashboard extends Component { searchRecipes, resetSearch, serviceStatus, - devRecipesCount, searchNeedle, + recipeFilter, + recipeDirectory, + openRecipeDirectory, + openDevDocs, + classes, + isCommunityRecipesPremiumFeature, + isUserPremiumUser, } = this.props; const { intl } = this.context; + + const communityRecipes = recipes.filter(r => !r.isDevRecipe); + const devRecipes = recipes.filter(r => r.isDevRecipe); + return (
@@ -122,20 +195,14 @@ export default @observer class RecipesDashboard extends Component { > {intl.formatMessage(messages.allRecipes)} - {devRecipesCount > 0 && ( - resetSearch()} - > - {intl.formatMessage(messages.devRecipes)} - {' '} -( - {devRecipesCount} -) - - )} + resetSearch()} + > + {intl.formatMessage(messages.customRecipes)} + {intl.formatMessage(messages.missingService)} {' '} @@ -146,23 +213,78 @@ export default @observer class RecipesDashboard extends Component { {isLoading ? ( ) : ( -
- {hasLoadedRecipes && recipes.length === 0 && ( -

- - - - {intl.formatMessage(messages.nothingFound)} -

+ <> + {recipeFilter === 'dev' && ( + <> +

+ {intl.formatMessage(messages.headlineCustomRecipes)} + {!isUserPremiumUser && ( + + )} +

+
+

+ {intl.formatMessage(messages.customRecipeIntro)} +

+ +
+
+
+ + )} + 0) && isCommunityRecipesPremiumFeature} + > + {recipeFilter === 'dev' && communityRecipes.length > 0 && ( +

{intl.formatMessage(messages.headlineCommunityRecipes)}

+ )} +
+ {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev'( +

+ + + + {intl.formatMessage(messages.nothingFound)} +

, + )} + {communityRecipes.map(recipe => ( + showAddServiceInterface({ recipeId: recipe.id })} + /> + ))} +
+
+ {recipeFilter === 'dev' && devRecipes.length > 0 && ( +
+

{intl.formatMessage(messages.headlineDevRecipes)}

+
+ {devRecipes.map(recipe => ( + showAddServiceInterface({ recipeId: recipe.id })} + /> + ))} +
+
)} - {recipes.map(recipe => ( - showAddServiceInterface({ recipeId: recipe.id })} - /> - ))} -
+ )}
diff --git a/src/config.js b/src/config.js index 5bc318545..544f94fde 100644 --- a/src/config.js +++ b/src/config.js @@ -67,6 +67,7 @@ export const DEFAULT_WINDOW_OPTIONS = { export const FRANZ_SERVICE_REQUEST = 'https://bit.ly/franz-plugin-docs'; export const FRANZ_TRANSLATION = 'https://bit.ly/franz-translate'; +export const FRANZ_DEV_DOCS = 'http://bit.ly/franz-dev-hub'; export const FILE_SYSTEM_SETTINGS_TYPES = [ 'app', diff --git a/src/containers/settings/RecipesScreen.js b/src/containers/settings/RecipesScreen.js index eda5ae54c..57e879f42 100644 --- a/src/containers/settings/RecipesScreen.js +++ b/src/containers/settings/RecipesScreen.js @@ -1,7 +1,9 @@ +import { remote, shell } from 'electron'; import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { autorun } from 'mobx'; import { inject, observer } from 'mobx-react'; +import path from 'path'; import RecipePreviewsStore from '../../stores/RecipePreviewsStore'; import RecipeStore from '../../stores/RecipesStore'; @@ -10,6 +12,11 @@ import UserStore from '../../stores/UserStore'; import RecipesDashboard from '../../components/settings/recipes/RecipesDashboard'; import ErrorBoundary from '../../components/util/ErrorBoundary'; +import { FRANZ_DEV_DOCS } from '../../config'; +import { gaEvent } from '../../lib/analytics'; +import { communityRecipesStore } from '../../features/communityRecipes'; + +const { app } = remote; export default @inject('stores', 'actions') @observer class RecipesScreen extends Component { static propTypes = { @@ -67,9 +74,16 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend render() { const { - recipePreviews, recipes, services, user, + recipePreviews, + recipes, + services, + user, } = this.props.stores; - const { showAddServiceInterface } = this.props.actions.service; + + const { + app: appActions, + service: serviceActions, + } = this.props.actions; const { filter } = this.props.params; let recipeFilter; @@ -77,7 +91,7 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend if (filter === 'all') { recipeFilter = recipePreviews.all; } else if (filter === 'dev') { - recipeFilter = recipePreviews.dev; + recipeFilter = communityRecipesStore.communityRecipes; } else { recipeFilter = recipePreviews.featured; } @@ -89,6 +103,8 @@ export default @inject('stores', 'actions') @observer class RecipesScreen extend || recipes.installRecipeRequest.isExecuting || recipePreviews.searchRecipePreviewsRequest.isExecuting; + const recipeDirectory = path.join(app.getPath('userData'), 'recipes', 'dev'); + return ( this.searchRecipes(e)} resetSearch={() => this.resetSearch()} searchNeedle={this.state.needle} serviceStatus={services.actionStatus} - devRecipesCount={recipePreviews.dev.length} + recipeFilter={filter} + recipeDirectory={recipeDirectory} + openRecipeDirectory={() => { + shell.openItem(recipeDirectory); + gaEvent('Recipe', 'open-recipe-folder', 'Open Folder'); + }} + openDevDocs={() => { + appActions.openExternalUrl({ url: FRANZ_DEV_DOCS }); + gaEvent('Recipe', 'open-dev-docs', 'Developer Documentation'); + }} + isCommunityRecipesPremiumFeature={communityRecipesStore.isCommunityRecipesPremiumFeature} + isUserPremiumUser={user.isPremium} /> ); @@ -117,6 +144,9 @@ RecipesScreen.wrappedComponent.propTypes = { user: PropTypes.instanceOf(UserStore).isRequired, }).isRequired, actions: PropTypes.shape({ + app: PropTypes.shape({ + openExternalUrl: PropTypes.func.isRequired, + }).isRequired, service: PropTypes.shape({ showAddServiceInterface: PropTypes.func.isRequired, }).isRequired, diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.js new file mode 100644 index 000000000..78e87855e --- /dev/null +++ b/src/features/communityRecipes/index.js @@ -0,0 +1,28 @@ +import { reaction } from 'mobx'; +import { CommunityRecipesStore } from './store'; + +const debug = require('debug')('Franz:feature:communityRecipes'); + +export const DEFAULT_SERVICE_LIMIT = 3; + +export const communityRecipesStore = new CommunityRecipesStore(); + +export default function initCommunityRecipes(stores, actions) { + const { features } = stores; + + communityRecipesStore.start(stores, actions); + + // Toggle communityRecipe premium status + reaction( + () => ( + features.features.isCommunityRecipesPremiumFeature + ), + (isPremiumFeature) => { + debug('Community recipes is premium feature: ', isPremiumFeature); + communityRecipesStore.isCommunityRecipesPremiumFeature = isPremiumFeature; + }, + { + fireImmediately: true, + }, + ); +} diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js new file mode 100644 index 000000000..165b753e8 --- /dev/null +++ b/src/features/communityRecipes/store.js @@ -0,0 +1,31 @@ +import { computed, observable } from 'mobx'; +import { FeatureStore } from '../utils/FeatureStore'; + +const debug = require('debug')('Franz:feature:communityRecipes:store'); + +export class CommunityRecipesStore extends FeatureStore { + @observable isCommunityRecipesPremiumFeature = false; + + start(stores, actions) { + debug('start'); + this.stores = stores; + this.actions = actions; + } + + stop() { + debug('stop'); + super.stop(); + } + + @computed get communityRecipes() { + if (!this.stores) return []; + + return this.stores.recipePreviews.dev.map((r) => { + r.isDevRecipe = !!r.author.find(a => a.email === this.stores.user.data.email); + + return r; + }); + } +} + +export default CommunityRecipesStore; diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 632eb38fd..9be12d369 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -1411,104 +1411,182 @@ "defaultMessage": "!!!Available Services", "end": { "column": 3, - "line": 18 + "line": 22 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.headline", "start": { "column": 12, - "line": 15 + "line": 19 } }, { "defaultMessage": "!!!Search service", "end": { "column": 3, - "line": 22 + "line": 26 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.searchService", "start": { "column": 17, - "line": 19 + "line": 23 } }, { "defaultMessage": "!!!Most popular", "end": { "column": 3, - "line": 26 + "line": 30 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.mostPopular", "start": { "column": 22, - "line": 23 + "line": 27 } }, { "defaultMessage": "!!!All services", "end": { "column": 3, - "line": 30 + "line": 34 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.all", "start": { "column": 14, - "line": 27 + "line": 31 } }, { - "defaultMessage": "!!!Development", + "defaultMessage": "!!!Custom Services", "end": { "column": 3, - "line": 34 + "line": 38 }, "file": "src/components/settings/recipes/RecipesDashboard.js", - "id": "settings.recipes.dev", + "id": "settings.recipes.custom", "start": { - "column": 14, - "line": 31 + "column": 17, + "line": 35 } }, { "defaultMessage": "!!!Sorry, but no service matched your search term.", "end": { "column": 3, - "line": 38 + "line": 42 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.nothingFound", "start": { "column": 16, - "line": 35 + "line": 39 } }, { "defaultMessage": "!!!Service successfully added", "end": { "column": 3, - "line": 42 + "line": 46 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.servicesSuccessfulAddedInfo", "start": { "column": 31, - "line": 39 + "line": 43 } }, { "defaultMessage": "!!!Missing a service?", "end": { "column": 3, - "line": 46 + "line": 50 }, "file": "src/components/settings/recipes/RecipesDashboard.js", "id": "settings.recipes.missingService", "start": { "column": 18, - "line": 43 + "line": 47 + } + }, + { + "defaultMessage": "!!!To add a custom service, copy the recipe folder into:", + "end": { + "column": 3, + "line": 54 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.intro", + "start": { + "column": 21, + "line": 51 + } + }, + { + "defaultMessage": "!!!Open directory", + "end": { + "column": 3, + "line": 58 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.openFolder", + "start": { + "column": 14, + "line": 55 + } + }, + { + "defaultMessage": "!!!Developer Documentation", + "end": { + "column": 3, + "line": 62 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.openDevDocs", + "start": { + "column": 15, + "line": 59 + } + }, + { + "defaultMessage": "!!!Custom Service Recipes", + "end": { + "column": 3, + "line": 66 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.headline.customRecipes", + "start": { + "column": 25, + "line": 63 + } + }, + { + "defaultMessage": "!!!Community Services", + "end": { + "column": 3, + "line": 70 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.headline.communityRecipes", + "start": { + "column": 28, + "line": 67 + } + }, + { + "defaultMessage": "!!!Your Development Service Recipes", + "end": { + "column": 3, + "line": 74 + }, + "file": "src/components/settings/recipes/RecipesDashboard.js", + "id": "settings.recipes.customService.headline.devRecipes", + "start": { + "column": 22, + "line": 71 } } ], @@ -3219,6 +3297,37 @@ ], "path": "src/features/announcements/components/AnnouncementScreen.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.", + "end": { + "column": 3, + "line": 14 + }, + "file": "src/features/communityRecipes/components/LimitReachedInfobox.js", + "id": "feature.serviceLimit.limitReached", + "start": { + "column": 16, + "line": 11 + } + }, + { + "defaultMessage": "!!!Upgrade account", + "end": { + "column": 3, + "line": 18 + }, + "file": "src/features/communityRecipes/components/LimitReachedInfobox.js", + "id": "premiumFeature.button.upgradeAccount", + "start": { + "column": 10, + "line": 15 + } + } + ], + "path": "src/features/communityRecipes/components/LimitReachedInfobox.json" + }, { "descriptors": [ { @@ -3359,6 +3468,37 @@ ], "path": "src/features/shareFranz/Component.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.", + "end": { + "column": 3, + "line": 14 + }, + "file": "src/features/unofficialRecipes/components/LimitReachedInfobox.js", + "id": "feature.serviceLimit.limitReached", + "start": { + "column": 16, + "line": 11 + } + }, + { + "defaultMessage": "!!!Upgrade account", + "end": { + "column": 3, + "line": 18 + }, + "file": "src/features/unofficialRecipes/components/LimitReachedInfobox.js", + "id": "premiumFeature.button.upgradeAccount", + "start": { + "column": 10, + "line": 15 + } + } + ], + "path": "src/features/unofficialRecipes/components/LimitReachedInfobox.json" + }, { "descriptors": [ { diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 6c2759dcc..8bffea63c 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -181,7 +181,13 @@ "settings.navigation.yourServices": "Your services", "settings.navigation.yourWorkspaces": "Your workspaces", "settings.recipes.all": "All services", - "settings.recipes.dev": "Development", + "settings.recipes.custom": "Custom Services", + "settings.recipes.customService.headline.communityRecipes": "Community Services", + "settings.recipes.customService.headline.customRecipes": "Custom Service Recipes", + "settings.recipes.customService.headline.devRecipes": "Your Development Service Recipes", + "settings.recipes.customService.intro": "To add a custom service, copy the service recipe to:", + "settings.recipes.customService.openDevDocs": "Developer Documentation", + "settings.recipes.customService.openFolder": "Open folder", "settings.recipes.headline": "Available services", "settings.recipes.missingService": "Missing a service?", "settings.recipes.mostPopular": "Most popular", diff --git a/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json b/src/i18n/messages/src/components/settings/recipes/RecipesDashboard.json index 7d9ed3283..26dcd3da5 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 @@ "defaultMessage": "!!!Available Services", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 15, + "line": 19, "column": 12 }, "end": { - "line": 18, + "line": 22, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Search service", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 19, + "line": 23, "column": 17 }, "end": { - "line": 22, + "line": 26, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Most popular", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 23, + "line": 27, "column": 22 }, "end": { - "line": 26, + "line": 30, "column": 3 } }, @@ -43,24 +43,24 @@ "defaultMessage": "!!!All services", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 27, + "line": 31, "column": 14 }, "end": { - "line": 30, + "line": 34, "column": 3 } }, { - "id": "settings.recipes.dev", - "defaultMessage": "!!!Development", + "id": "settings.recipes.custom", + "defaultMessage": "!!!Custom Services", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 31, - "column": 14 + "line": 35, + "column": 17 }, "end": { - "line": 34, + "line": 38, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Sorry, but no service matched your search term.", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 35, + "line": 39, "column": 16 }, "end": { - "line": 38, + "line": 42, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Service successfully added", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 39, + "line": 43, "column": 31 }, "end": { - "line": 42, + "line": 46, "column": 3 } }, @@ -95,11 +95,89 @@ "defaultMessage": "!!!Missing a service?", "file": "src/components/settings/recipes/RecipesDashboard.js", "start": { - "line": 43, + "line": 47, "column": 18 }, "end": { - "line": 46, + "line": 50, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.intro", + "defaultMessage": "!!!To add a custom service, copy the recipe folder into:", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 51, + "column": 21 + }, + "end": { + "line": 54, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.openFolder", + "defaultMessage": "!!!Open directory", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 55, + "column": 14 + }, + "end": { + "line": 58, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.openDevDocs", + "defaultMessage": "!!!Developer Documentation", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 59, + "column": 15 + }, + "end": { + "line": 62, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.headline.customRecipes", + "defaultMessage": "!!!Custom Service Recipes", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 63, + "column": 25 + }, + "end": { + "line": 66, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.headline.communityRecipes", + "defaultMessage": "!!!Community Services", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 67, + "column": 28 + }, + "end": { + "line": 70, + "column": 3 + } + }, + { + "id": "settings.recipes.customService.headline.devRecipes", + "defaultMessage": "!!!Your Development Service Recipes", + "file": "src/components/settings/recipes/RecipesDashboard.js", + "start": { + "line": 71, + "column": 22 + }, + "end": { + "line": 74, "column": 3 } } diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index e7832088b..0f54a50af 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -16,6 +16,7 @@ import workspaces from '../features/workspaces'; import shareFranz from '../features/shareFranz'; import announcements from '../features/announcements'; import settingsWS from '../features/settingsWS'; +import communityRecipes from '../features/communityRecipes'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -75,5 +76,6 @@ export default class FeaturesStore extends Store { shareFranz(this.stores, this.actions); announcements(this.stores, this.actions); settingsWS(this.stores, this.actions); + communityRecipes(this.stores, this.actions); } } diff --git a/src/stores/index.js b/src/stores/index.js index 1912418a2..7f89bf1fb 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -12,6 +12,7 @@ import RequestStore from './RequestStore'; import GlobalErrorStore from './GlobalErrorStore'; import { workspaceStore } from '../features/workspaces'; import { announcementsStore } from '../features/announcements'; +import { communityRecipesStore } from '../features/communityRecipes'; export default (api, actions, router) => { const stores = {}; @@ -31,6 +32,7 @@ export default (api, actions, router) => { globalError: new GlobalErrorStore(stores, api, actions), workspaces: workspaceStore, announcements: announcementsStore, + communityRecipes: communityRecipesStore, }); // Initialize all stores Object.keys(stores).forEach((name) => { diff --git a/src/styles/recipes.scss b/src/styles/recipes.scss index 84222e1fe..56a248e98 100644 --- a/src/styles/recipes.scss +++ b/src/styles/recipes.scss @@ -12,7 +12,7 @@ display: flex; flex-flow: row wrap; height: auto; - min-height: 70%; + // min-height: 70%; &.recipes__list--disabled { filter: grayscale(100%); -- cgit v1.2.3-70-g09d2 From 66647005ae23bccf1691a43ea36565cae4ce9518 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 13 Jun 2019 15:15:38 +0200 Subject: Improve serviceLimit implementation --- src/components/settings/navigation/SettingsNavigation.js | 8 +++++++- src/features/serviceLimit/components/LimitReachedInfobox.js | 4 ++++ src/features/serviceLimit/index.js | 2 +- src/features/serviceLimit/store.js | 6 +++--- src/i18n/locales/en-US.json | 2 +- 5 files changed, 16 insertions(+), 6 deletions(-) (limited to 'src/components') 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'; import { workspaceStore } from '../../../features/workspaces'; import UIStore from '../../../stores/UIStore'; import UserStore from '../../../stores/UserStore'; +import { serviceLimitStore } from '../../../features/serviceLimit'; const messages = defineMessages({ availableServices: { @@ -80,7 +81,12 @@ export default @inject('stores') @observer class SettingsNavigation extends Comp > {intl.formatMessage(messages.yourServices)} {' '} - {serviceCount} + + {serviceCount} + {serviceLimitStore.serviceLimit !== 0 && ( + `/${serviceLimitStore.serviceLimit}` + )} + {workspaceStore.isFeatureEnabled ? ( ({ borderRadius: 0, marginBottom: 0, + '& > div': { + marginBottom: 0, + }, + '& button': { color: theme.styleTypes.primary.contrast, }, diff --git a/src/features/serviceLimit/index.js b/src/features/serviceLimit/index.js index 76f996195..92ad8bb98 100644 --- a/src/features/serviceLimit/index.js +++ b/src/features/serviceLimit/index.js @@ -15,7 +15,7 @@ export default function initServiceLimit(stores, actions) { // Toggle serviceLimit feature reaction( () => ( - features.features.hasServiceLimit + features.features.isServiceLimitEnabled ), (isEnabled) => { if (isEnabled) { diff --git a/src/features/serviceLimit/store.js b/src/features/serviceLimit/store.js index 752f71371..9836c5f51 100644 --- a/src/features/serviceLimit/store.js +++ b/src/features/serviceLimit/store.js @@ -24,12 +24,12 @@ export class ServiceLimitStore extends FeatureStore { @computed get userHasReachedServiceLimit() { if (!this.isServiceLimitEnabled) return false; - const { user } = this.stores; - - return !user.isPremium && this.serviceCount >= this.serviceLimit; + return this.serviceLimit !== 0 && this.serviceCount >= this.serviceLimit; } @computed get serviceLimit() { + if (!this.isServiceLimitEnabled || this.stores.features.features.serviceLimitCount === 0) return 0; + return this.stores.features.features.serviceLimitCount || DEFAULT_SERVICE_LIMIT; } diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index a821dbe39..eded7b79b 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -5,7 +5,7 @@ "feature.delayApp.action": "Get a Franz Supporter License", "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", "feature.delayApp.text": "Franz will continue in {seconds} seconds.", - "feature.serviceLimit.limitReached": "You have added {amount} of {limit} services. Please upgrade your account to add more services.", + "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.", "feature.shareFranz.action.email": "Send as email", "feature.shareFranz.action.facebook": "Share on Facebook", "feature.shareFranz.action.twitter": "Share on Twitter", -- cgit v1.2.3-70-g09d2 From 97852499feba82003021c501b97b075e11647f5e Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 13 Jun 2019 15:16:57 +0200 Subject: Add "service limit reached" screen --- .../services/content/ServiceRestricted.js | 54 +++++++ src/components/services/content/ServiceView.js | 25 ++- src/components/services/content/Services.js | 1 + src/i18n/locales/defaultMessages.json | 174 +++++++++++++-------- src/i18n/locales/en-US.json | 3 + .../services/content/ServiceRestricted.json | 41 +++++ .../settings/navigation/SettingsNavigation.json | 32 ++-- src/models/Service.js | 2 + src/stores/ServicesStore.js | 24 ++- 9 files changed, 268 insertions(+), 88 deletions(-) create mode 100644 src/components/services/content/ServiceRestricted.js create mode 100644 src/i18n/messages/src/components/services/content/ServiceRestricted.json (limited to 'src/components') diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js new file mode 100644 index 000000000..9fb7d0961 --- /dev/null +++ b/src/components/services/content/ServiceRestricted.js @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; + +import { serviceLimitStore } from '../../../features/serviceLimit'; +import Button from '../../ui/Button'; + +const messages = defineMessages({ + headline: { + id: 'service.restrictedHandler.headline', + defaultMessage: '!!!You have reached your service limit.', + }, + text: { + id: 'service.restrictedHandler.text', + defaultMessage: '!!!Please upgrade your account to use more than {count} services.', + }, + action: { + id: 'service.restrictedHandler.action', + defaultMessage: '!!!Upgrade Account', + }, +}); + +export default @observer class ServiceRestricted extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + upgrade: PropTypes.func.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + countdownInterval = null; + + countdownIntervalTimeout = 1000; + + render() { + const { name, upgrade } = this.props; + const { intl } = this.context; + + return ( +
+

{intl.formatMessage(messages.headline)}

+

{intl.formatMessage(messages.text, { count: serviceLimitStore.serviceLimit })}

+
+ ); + } +} diff --git a/src/components/services/content/ServiceView.js b/src/components/services/content/ServiceView.js index 13148b9b3..18279fd06 100644 --- a/src/components/services/content/ServiceView.js +++ b/src/components/services/content/ServiceView.js @@ -10,6 +10,7 @@ import WebviewLoader from '../../ui/WebviewLoader'; import WebviewCrashHandler from './WebviewCrashHandler'; import WebviewErrorHandler from './ErrorHandlers/WebviewErrorHandler'; import ServiceDisabled from './ServiceDisabled'; +import ServiceRestricted from './ServiceRestricted'; import ServiceWebview from './ServiceWebview'; export default @observer class ServiceView extends Component { @@ -21,6 +22,7 @@ export default @observer class ServiceView extends Component { edit: PropTypes.func.isRequired, enable: PropTypes.func.isRequired, isActive: PropTypes.bool, + upgrade: PropTypes.func.isRequired, }; static defaultProps = { @@ -72,6 +74,7 @@ export default @observer class ServiceView extends Component { reload, edit, enable, + upgrade, } = this.props; const webviewClasses = classnames({ @@ -99,7 +102,7 @@ export default @observer class ServiceView extends Component { reload={reload} /> )} - {service.isEnabled && service.isLoading && service.isFirstLoad && ( + {service.isEnabled && service.isLoading && service.isFirstLoad && !service.isServiceAccessRestricted && ( ) : ( - + <> + {service.isServiceAccessRestricted ? ( + + ) : ( + + )} + )} {statusBar}
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js index 8f8c38a11..48de0ebaa 100644 --- a/src/components/services/content/Services.js +++ b/src/components/services/content/Services.js @@ -89,6 +89,7 @@ export default @observer class Services extends Component { }, redirect: false, })} + upgrade={() => openSettings({ path: 'user' })} /> ))}
diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index a5572bd32..533944fd0 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -891,6 +891,50 @@ ], "path": "src/components/services/content/ServiceDisabled.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!You have reached your service limit.", + "end": { + "column": 3, + "line": 13 + }, + "file": "src/components/services/content/ServiceRestricted.js", + "id": "service.restrictedHandler.headline", + "start": { + "column": 12, + "line": 10 + } + }, + { + "defaultMessage": "!!!Please upgrade your account to use more than {count} services.", + "end": { + "column": 3, + "line": 17 + }, + "file": "src/components/services/content/ServiceRestricted.js", + "id": "service.restrictedHandler.text", + "start": { + "column": 8, + "line": 14 + } + }, + { + "defaultMessage": "!!!Upgrade Account", + "end": { + "column": 3, + "line": 21 + }, + "file": "src/components/services/content/ServiceRestricted.js", + "id": "service.restrictedHandler.action", + "start": { + "column": 10, + "line": 18 + } + } + ], + "path": "src/components/services/content/ServiceRestricted.json" + }, { "descriptors": [ { @@ -1307,104 +1351,104 @@ "defaultMessage": "!!!Available services", "end": { "column": 3, - "line": 16 + "line": 17 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.availableServices", "start": { "column": 21, - "line": 13 + "line": 14 } }, { "defaultMessage": "!!!Your services", "end": { "column": 3, - "line": 20 + "line": 21 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.yourServices", "start": { "column": 16, - "line": 17 + "line": 18 } }, { "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 24 + "line": 25 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.yourWorkspaces", "start": { "column": 18, - "line": 21 + "line": 22 } }, { "defaultMessage": "!!!Account", "end": { "column": 3, - "line": 28 + "line": 29 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.account", "start": { "column": 11, - "line": 25 + "line": 26 } }, { "defaultMessage": "!!!Manage Team", "end": { "column": 3, - "line": 32 + "line": 33 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.team", "start": { "column": 8, - "line": 29 + "line": 30 } }, { "defaultMessage": "!!!Settings", "end": { "column": 3, - "line": 36 + "line": 37 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.settings", "start": { "column": 12, - "line": 33 + "line": 34 } }, { "defaultMessage": "!!!Invite Friends", "end": { "column": 3, - "line": 40 + "line": 41 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.inviteFriends", "start": { "column": 17, - "line": 37 + "line": 38 } }, { "defaultMessage": "!!!Logout", "end": { "column": 3, - "line": 44 + "line": 45 }, "file": "src/components/settings/navigation/SettingsNavigation.js", "id": "settings.navigation.logout", "start": { "column": 10, - "line": 41 + "line": 42 } } ], @@ -1525,286 +1569,286 @@ "defaultMessage": "!!!Save service", "end": { "column": 3, - "line": 26 + "line": 27 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.saveButton", "start": { "column": 15, - "line": 23 + "line": 24 } }, { "defaultMessage": "!!!Delete Service", "end": { "column": 3, - "line": 30 + "line": 31 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.deleteButton", "start": { "column": 17, - "line": 27 + "line": 28 } }, { "defaultMessage": "!!!Available services", "end": { "column": 3, - "line": 34 + "line": 35 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.availableServices", "start": { "column": 21, - "line": 31 + "line": 32 } }, { "defaultMessage": "!!!Your services", "end": { "column": 3, - "line": 38 + "line": 39 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.yourServices", "start": { "column": 16, - "line": 35 + "line": 36 } }, { "defaultMessage": "!!!Add {name}", "end": { "column": 3, - "line": 42 + "line": 43 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.addServiceHeadline", "start": { "column": 22, - "line": 39 + "line": 40 } }, { "defaultMessage": "!!!Edit {name}", "end": { "column": 3, - "line": 46 + "line": 47 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.editServiceHeadline", "start": { "column": 23, - "line": 43 + "line": 44 } }, { "defaultMessage": "!!!Hosted", "end": { "column": 3, - "line": 50 + "line": 51 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.tabHosted", "start": { "column": 13, - "line": 47 + "line": 48 } }, { "defaultMessage": "!!!Self hosted ⭐️", "end": { "column": 3, - "line": 54 + "line": 55 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.tabOnPremise", "start": { "column": 16, - "line": 51 + "line": 52 } }, { "defaultMessage": "!!!Use the hosted {name} service.", "end": { "column": 3, - "line": 58 + "line": 59 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.useHostedService", "start": { "column": 20, - "line": 55 + "line": 56 } }, { "defaultMessage": "!!!Could not validate custom {name} server.", "end": { "column": 3, - "line": 62 + "line": 63 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.customUrlValidationError", "start": { "column": 28, - "line": 59 + "line": 60 } }, { "defaultMessage": "!!!To add self hosted services, you need a Franz Premium Supporter Account.", "end": { "column": 3, - "line": 66 + "line": 67 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.customUrlPremiumInfo", "start": { "column": 24, - "line": 63 + "line": 64 } }, { "defaultMessage": "!!!Upgrade your account", "end": { "column": 3, - "line": 70 + "line": 71 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.customUrlUpgradeAccount", "start": { "column": 27, - "line": 67 + "line": 68 } }, { "defaultMessage": "!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...", "end": { "column": 3, - "line": 74 + "line": 75 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.indirectMessageInfo", "start": { "column": 23, - "line": 71 + "line": 72 } }, { "defaultMessage": "!!!When disabled, all notification sounds and audio playback are muted", "end": { "column": 3, - "line": 78 + "line": 79 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.isMutedInfo", "start": { "column": 15, - "line": 75 + "line": 76 } }, { "defaultMessage": "!!!Notifications", "end": { "column": 3, - "line": 82 + "line": 83 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.headlineNotifications", "start": { "column": 25, - "line": 79 + "line": 80 } }, { "defaultMessage": "!!!Unread message badges", "end": { "column": 3, - "line": 86 + "line": 87 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.headlineBadges", "start": { "column": 18, - "line": 83 + "line": 84 } }, { "defaultMessage": "!!!General", "end": { "column": 3, - "line": 90 + "line": 91 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.headlineGeneral", "start": { "column": 19, - "line": 87 + "line": 88 } }, { "defaultMessage": "!!!Delete", "end": { "column": 3, - "line": 94 + "line": 95 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.iconDelete", "start": { "column": 14, - "line": 91 + "line": 92 } }, { "defaultMessage": "!!!Drop your image, or click here", "end": { "column": 3, - "line": 98 + "line": 99 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.iconUpload", "start": { "column": 14, - "line": 95 + "line": 96 } }, { "defaultMessage": "!!!HTTP/HTTPS Proxy Settings", "end": { "column": 3, - "line": 102 + "line": 103 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.proxy.headline", "start": { "column": 17, - "line": 99 + "line": 100 } }, { "defaultMessage": "!!!Please restart Franz after changing proxy Settings.", "end": { "column": 3, - "line": 106 + "line": 107 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.proxy.restartInfo", "start": { "column": 20, - "line": 103 + "line": 104 } }, { "defaultMessage": "!!!Proxy settings will not be synchronized with the Franz servers.", "end": { "column": 3, - "line": 110 + "line": 111 }, "file": "src/components/settings/services/EditServiceForm.js", "id": "settings.service.form.proxy.info", "start": { "column": 13, - "line": 107 + "line": 108 } } ], @@ -3289,29 +3333,29 @@ { "descriptors": [ { - "defaultMessage": "!!!You have added {amount} of {amount} services. Please upgrade your account to add more services.", + "defaultMessage": "!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.", "end": { "column": 3, - "line": 16 + "line": 14 }, "file": "src/features/serviceLimit/components/LimitReachedInfobox.js", "id": "feature.serviceLimit.limitReached", "start": { "column": 16, - "line": 13 + "line": 11 } }, { "defaultMessage": "!!!Upgrade account", "end": { "column": 3, - "line": 20 + "line": 18 }, "file": "src/features/serviceLimit/components/LimitReachedInfobox.js", "id": "premiumFeature.button.upgradeAccount", "start": { "column": 10, - "line": 17 + "line": 15 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index eded7b79b..d83ce37b0 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -119,6 +119,9 @@ "service.errorHandler.headline": "Oh no!", "service.errorHandler.message": "Error", "service.errorHandler.text": "{name} has failed to load.", + "service.restrictedHandler.action": "Upgrade Account", + "service.restrictedHandler.headline": "You have reached your service limit.", + "service.restrictedHandler.text": "Please upgrade your account to use more than {count} services.", "service.webviewLoader.loading": "Loading", "services.getStarted": "Get started", "services.welcome": "Welcome to Franz", 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..30ecd4d75 --- /dev/null +++ b/src/i18n/messages/src/components/services/content/ServiceRestricted.json @@ -0,0 +1,41 @@ +[ + { + "id": "service.restrictedHandler.headline", + "defaultMessage": "!!!You have reached your service limit.", + "file": "src/components/services/content/ServiceRestricted.js", + "start": { + "line": 10, + "column": 12 + }, + "end": { + "line": 13, + "column": 3 + } + }, + { + "id": "service.restrictedHandler.text", + "defaultMessage": "!!!Please upgrade your account to use more than {count} services.", + "file": "src/components/services/content/ServiceRestricted.js", + "start": { + "line": 14, + "column": 8 + }, + "end": { + "line": 17, + "column": 3 + } + }, + { + "id": "service.restrictedHandler.action", + "defaultMessage": "!!!Upgrade Account", + "file": "src/components/services/content/ServiceRestricted.js", + "start": { + "line": 18, + "column": 10 + }, + "end": { + "line": 21, + "column": 3 + } + } +] \ No newline at end of file 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 @@ "defaultMessage": "!!!Available services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 13, + "line": 14, "column": 21 }, "end": { - "line": 16, + "line": 17, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Your services", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 17, + "line": 18, "column": 16 }, "end": { - "line": 20, + "line": 21, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Your workspaces", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 21, + "line": 22, "column": 18 }, "end": { - "line": 24, + "line": 25, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Account", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 25, + "line": 26, "column": 11 }, "end": { - "line": 28, + "line": 29, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Manage Team", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 29, + "line": 30, "column": 8 }, "end": { - "line": 32, + "line": 33, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Settings", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 33, + "line": 34, "column": 12 }, "end": { - "line": 36, + "line": 37, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Invite Friends", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 37, + "line": 38, "column": 17 }, "end": { - "line": 40, + "line": 41, "column": 3 } }, @@ -95,11 +95,11 @@ "defaultMessage": "!!!Logout", "file": "src/components/settings/navigation/SettingsNavigation.js", "start": { - "line": 41, + "line": 42, "column": 10 }, "end": { - "line": 44, + "line": 45, "column": 3 } } diff --git a/src/models/Service.js b/src/models/Service.js index 88bce3360..fa3648a39 100644 --- a/src/models/Service.js +++ b/src/models/Service.js @@ -104,6 +104,8 @@ export default class Service { this.recipe = recipe; + this.isServiceAccessRestricted = false; + autorun(() => { if (!this.isEnabled) { this.webview = null; diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index ee47bf6db..79d1a0ea1 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js @@ -93,13 +93,21 @@ export default class ServicesStore extends Store { () => this.stores.settings.app.spellcheckerLanguage, () => this._shareSettingsWithServiceProcess(), ); + + reaction( + () => this.all, + () => this._restrictServiceAccess(), + ); } @computed get all() { if (this.stores.user.isLoggedIn) { const services = this.allServicesRequest.execute().result; if (services) { - return observable(services.slice().slice().sort((a, b) => a.order - b.order)); + return observable(services.slice().slice().sort((a, b) => a.order - b.order).map((s, index) => { + s.index = index; + return s; + })); } } return []; @@ -681,6 +689,20 @@ export default class ServicesStore extends Store { return serviceData; } + _restrictServiceAccess() { + const services = this.all; + const { userHasReachedServiceLimit, serviceLimit } = this.stores.serviceLimit; + + if (userHasReachedServiceLimit) { + services.map((service, index) => { + console.log('restrictServiceAcceess', index >= serviceLimit); + service.isServiceAccessRestricted = index >= serviceLimit; + + return service; + }); + } + } + // Helper _initializeServiceRecipeInWebview(serviceId) { const service = this.one(serviceId); -- cgit v1.2.3-70-g09d2 From d81b588a5ffe752a1eddba0df4ef01d8672989eb Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 13 Jun 2019 16:49:38 +0200 Subject: Fix issue with dev recipe list --- src/components/settings/recipes/RecipesDashboard.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'src/components') diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js index e2e8121e5..dce4758f0 100644 --- a/src/components/settings/recipes/RecipesDashboard.js +++ b/src/components/settings/recipes/RecipesDashboard.js @@ -118,7 +118,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com openDevDocs: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, isCommunityRecipesPremiumFeature: PropTypes.bool.isRequired, - isUserPremiumUser: PropTypes.bool.isRequired, }; static defaultProps = { @@ -146,7 +145,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com openDevDocs, classes, isCommunityRecipesPremiumFeature, - isUserPremiumUser, } = this.props; const { intl } = this.context; @@ -218,7 +216,7 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com <>

{intl.formatMessage(messages.headlineCustomRecipes)} - {!isUserPremiumUser && ( + {isCommunityRecipesPremiumFeature && ( )}

@@ -253,13 +251,13 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com

{intl.formatMessage(messages.headlineCommunityRecipes)}

)}
- {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev'( + {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && (

{intl.formatMessage(messages.nothingFound)} -

, +

)} {communityRecipes.map(recipe => ( Date: Fri, 14 Jun 2019 15:29:04 +0200 Subject: Restrict services with customURL when not premium user --- .../services/content/ServiceRestricted.js | 38 ++++++++++++++---- src/components/services/content/ServiceView.js | 2 +- src/i18n/locales/defaultMessages.json | 46 +++++++++++++++++----- src/i18n/locales/en-US.json | 6 ++- .../services/content/ServiceRestricted.json | 46 +++++++++++++++++----- src/models/Service.js | 17 +++++++- src/stores/ServicesStore.js | 34 ++++++++++------ 7 files changed, 145 insertions(+), 44 deletions(-) (limited to 'src/components') diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js index 9fb7d0961..4b8d926aa 100644 --- a/src/components/services/content/ServiceRestricted.js +++ b/src/components/services/content/ServiceRestricted.js @@ -5,16 +5,25 @@ import { defineMessages, intlShape } from 'react-intl'; import { serviceLimitStore } from '../../../features/serviceLimit'; import Button from '../../ui/Button'; +import { RESTRICTION_TYPES } from '../../../models/Service'; const messages = defineMessages({ - headline: { - id: 'service.restrictedHandler.headline', + headlineServiceLimit: { + id: 'service.restrictedHandler.serviceLimit.headline', defaultMessage: '!!!You have reached your service limit.', }, - text: { - id: 'service.restrictedHandler.text', + textServiceLimit: { + id: 'service.restrictedHandler.serviceLimit.text', defaultMessage: '!!!Please upgrade your account to use more than {count} services.', }, + headlineCustomUrl: { + id: 'service.restrictedHandler.customUrl.headline', + defaultMessage: '!!!Franz Professional Plan required', + }, + textCustomUrl: { + id: 'service.restrictedHandler.customUrl.text', + defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.', + }, action: { id: 'service.restrictedHandler.action', defaultMessage: '!!!Upgrade Account', @@ -25,6 +34,7 @@ export default @observer class ServiceRestricted extends Component { static propTypes = { name: PropTypes.string.isRequired, upgrade: PropTypes.func.isRequired, + type: PropTypes.number.isRequired, }; static contextTypes = { @@ -36,13 +46,27 @@ export default @observer class ServiceRestricted extends Component { countdownIntervalTimeout = 1000; render() { - const { name, upgrade } = this.props; + const { + name, + upgrade, + type, + } = this.props; const { intl } = this.context; return (
-

{intl.formatMessage(messages.headline)}

-

{intl.formatMessage(messages.text, { count: serviceLimitStore.serviceLimit })}

+ {type === RESTRICTION_TYPES.SERVICE_LIMIT && ( + <> +

{intl.formatMessage(messages.headlineServiceLimit)}

+

{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}

+ + )} + {type === RESTRICTION_TYPES.CUSTOM_URL && ( + <> +

{intl.formatMessage(messages.headlineCustomUrl)}

+

{intl.formatMessage(messages.textCustomUrl)}

+ + )}
@@ -355,7 +355,7 @@ export default @observer class EditServiceForm extends Component { {isProxyFeatureEnabled && (
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index efd453356..6d0811b1b 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 { isClearingAllCache: PropTypes.bool.isRequired, onClearAllCache: PropTypes.func.isRequired, cacheSize: PropTypes.string.isRequired, - isSpellcheckerPremiumFeature: PropTypes.bool.isRequired, + isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired, }; static contextTypes = { @@ -130,7 +130,7 @@ export default @observer class EditSettingsForm extends Component { isClearingAllCache, onClearAllCache, cacheSize, - isSpellcheckerPremiumFeature, + isSpellcheckerIncludedInCurrentPlan, } = this.props; const { intl } = this.context; @@ -143,6 +143,8 @@ export default @observer class EditSettingsForm extends Component { updateButtonLabelMessage = messages.buttonSearchForUpdate; } + console.log('isSpellcheckerIncludedInCurrentPlan', isSpellcheckerIncludedInCurrentPlan); + return (
@@ -173,7 +175,7 @@ export default @observer class EditSettingsForm extends Component {

{intl.formatMessage(messages.headlineLanguage)}

@@ -162,9 +143,6 @@ export default @observer class Signup extends Component { showPasswordToggle scorePassword /> - {form.$('accountType').value === 'company' && ( - - )} {error.code === 'email-duplicate' && (

{intl.formatMessage(messages.emailDuplicate)}

)} diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js index 81b1c713c..9fe1c93b7 100644 --- a/src/components/subscription/TrialForm.js +++ b/src/components/subscription/TrialForm.js @@ -15,6 +15,10 @@ const messages = defineMessages({ id: 'subscription.cta.activateTrial', defaultMessage: '!!!Yes, start the free Franz Professional trial', }, + allOptionsButton: { + id: 'subscription.cta.allOptions', + defaultMessage: '!!!See all options', + }, teaserHeadline: { id: 'settings.account.headlineTrialUpgrade', defaultMessage: '!!!Get the free 14 day Franz Professional Trial', @@ -39,7 +43,12 @@ const messages = defineMessages({ const styles = () => ({ activateTrialButton: { - margin: [40, 0, 50], + margin: [40, 0, 10], + }, + allOptionsButton: { + margin: [0, 0, 40], + background: 'none', + border: 'none', }, keyTerms: { marginTop: 20, @@ -50,6 +59,7 @@ export default @observer @injectSheet(styles) class TrialForm extends Component static propTypes = { activateTrial: PropTypes.func.isRequired, isActivatingTrial: PropTypes.bool.isRequired, + showAllOptions: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, }; @@ -61,6 +71,7 @@ export default @observer @injectSheet(styles) class TrialForm extends Component const { isActivatingTrial, activateTrial, + showAllOptions, classes, } = this.props; const { intl } = this.context; @@ -83,6 +94,12 @@ export default @observer @injectSheet(styles) class TrialForm extends Component onClick={activateTrial} stretch /> +
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js index 7f3688828..65afc985b 100644 --- a/src/features/todos/containers/TodosScreen.js +++ b/src/features/todos/containers/TodosScreen.js @@ -26,7 +26,6 @@ class TodosScreen extends Component { minWidth={TODOS_MIN_WIDTH} resize={width => todoActions.resize({ width })} isTodosIncludedInCurrentPlan={this.props.stores.features.features.isTodosIncludedInCurrentPlan || false} - upgradeAccount={() => this.props.actions.ui.openSettings({ path: 'user' })} /> ); @@ -39,9 +38,4 @@ TodosScreen.wrappedComponent.propTypes = { stores: PropTypes.shape({ features: PropTypes.instanceOf(FeaturesStore).isRequired, }).isRequired, - actions: PropTypes.shape({ - ui: PropTypes.shape({ - openSettings: PropTypes.func.isRequired, - }).isRequired, - }).isRequired, }; diff --git a/src/features/todos/store.js b/src/features/todos/store.js index 7da3b7f49..56e117c6c 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js @@ -12,6 +12,7 @@ import { createReactions } from '../../stores/lib/Reaction'; import { createActionBindings } from '../utils/ActionBinding'; import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.'; import { IPC } from './constants'; +import { state as delayAppState } from '../delayApp'; const debug = require('debug')('Franz:feature:todos:store'); @@ -29,7 +30,7 @@ export default class TodoStore extends FeatureStore { } @computed get isTodosPanelVisible() { - if (this.stores.services.all.length === 0) return false; + if (this.stores.services.all.length === 0 || delayAppState.isDelayAppScreenVisible) return false; if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE; return this.settings.isTodosPanelVisible; diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index 09c98ab8c..9e06a78e3 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js @@ -1,6 +1,6 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; +import { observer, PropTypes as MobxPropTypes, inject } from 'mobx-react'; import { defineMessages, intlShape } from 'react-intl'; import injectSheet from 'react-jss'; import { Infobox } from '@meetfranz/ui'; @@ -12,6 +12,8 @@ import Request from '../../../stores/lib/Request'; import Appear from '../../../components/ui/effects/Appear'; import { workspaceStore } from '../index'; import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; +import UIStore from '../../../stores/UIStore'; +import ActivateTrialButton from '../../../components/ui/activateTrialButton'; const messages = defineMessages({ headline: { @@ -62,17 +64,27 @@ const styles = theme => ({ height: 'auto', }, premiumAnnouncement: { - padding: '20px', - backgroundColor: '#3498db', - marginLeft: '-20px', - marginBottom: '20px', + padding: 20, + // backgroundColor: '#3498db', + marginLeft: -20, + marginBottom: 40, + paddingBottom: 40, height: 'auto', - color: 'white', - borderRadius: theme.borderRadius, + display: 'flex', + borderBottom: [1, 'solid', theme.inputBackground], + }, + teaserImage: { + width: 200, + height: '100%', + float: 'left', + margin: [-8, 0, 0, -20], + }, + upgradeCTA: { + marginTop: 20, }, }); -@injectSheet(styles) @observer +@inject('stores') @injectSheet(styles) @observer class WorkspacesDashboard extends Component { static propTypes = { classes: PropTypes.object.isRequired, @@ -100,7 +112,9 @@ class WorkspacesDashboard extends Component { onWorkspaceClick, workspaces, } = this.props; + const { intl } = this.context; + return (
@@ -138,13 +152,21 @@ class WorkspacesDashboard extends Component { {workspaceStore.isPremiumUpgradeRequired && (
-

{intl.formatMessage(messages.workspaceFeatureHeadline)}

-

{intl.formatMessage(messages.workspaceFeatureInfo)}

+ +
+

{intl.formatMessage(messages.workspaceFeatureHeadline)}

+

{intl.formatMessage(messages.workspaceFeatureInfo)}

+ +
)} workspaceStore.isPremiumUpgradeRequired} gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }} > {/* ===== Create workspace form ===== */} @@ -207,3 +229,9 @@ class WorkspacesDashboard extends Component { } export default WorkspacesDashboard; + +WorkspacesDashboard.wrappedComponent.propTypes = { + stores: PropTypes.shape({ + ui: PropTypes.instanceOf(UIStore).isRequired, + }).isRequired, +}; diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index e44569be9..4a1f80b4e 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js @@ -253,11 +253,10 @@ export default class WorkspacesStore extends FeatureStore { }; _setIsPremiumFeatureReaction = () => { - const { features, user } = this.stores; - const { isPremium } = user.data; + const { features } = this.stores; const { isWorkspaceIncludedInCurrentPlan } = features.features; this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan; - this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan && !isPremium; + this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan; }; _setWorkspaceBeingEditedReaction = () => { diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js index 19392585e..e0f1fd89a 100644 --- a/src/helpers/plan-helpers.js +++ b/src/helpers/plan-helpers.js @@ -33,3 +33,13 @@ export function i18nPlanName(planId, intl) { return intl.formatMessage(messages[plan]); } + +export function getPlan(planId) { + if (!planId) { + throw new Error('planId is required'); + } + + const plan = PLANS_MAPPING[planId]; + + return plan; +} diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index dabe2f11f..367184c01 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -2892,6 +2892,63 @@ ], "path": "src/components/TrialActivationInfoBar.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!Get a Franz Supporter License", + "end": { + "column": 3, + "line": 16 + }, + "file": "src/components/ui/ActivateTrialButton/index.js", + "id": "feature.delayApp.upgrade.action", + "start": { + "column": 10, + "line": 13 + } + }, + { + "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional", + "end": { + "column": 3, + "line": 20 + }, + "file": "src/components/ui/ActivateTrialButton/index.js", + "id": "feature.delayApp.trial.action", + "start": { + "column": 15, + "line": 17 + } + }, + { + "defaultMessage": "!!!Upgrade account", + "end": { + "column": 3, + "line": 24 + }, + "file": "src/components/ui/ActivateTrialButton/index.js", + "id": "feature.delayApp.upgrade.actionShort", + "start": { + "column": 15, + "line": 21 + } + }, + { + "defaultMessage": "!!!Activate the free Franz Professional trial", + "end": { + "column": 3, + "line": 28 + }, + "file": "src/components/ui/ActivateTrialButton/index.js", + "id": "feature.delayApp.trial.actionShort", + "start": { + "column": 20, + "line": 25 + } + } + ], + "path": "src/components/ui/ActivateTrialButton/index.json" + }, { "descriptors": [ { @@ -3033,13 +3090,13 @@ "defaultMessage": "!!!Upgrade account", "end": { "column": 3, - "line": 18 + "line": 19 }, "file": "src/components/ui/PremiumFeatureContainer/index.js", "id": "premiumFeature.button.upgradeAccount", "start": { "column": 10, - "line": 15 + "line": 16 } } ], @@ -3720,91 +3777,91 @@ "defaultMessage": "!!!Franz is better together!", "end": { "column": 3, - "line": 19 + "line": 21 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.headline", "start": { "column": 12, - "line": 16 + "line": 18 } }, { "defaultMessage": "!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.", "end": { "column": 3, - "line": 23 + "line": 25 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.text", "start": { "column": 8, - "line": 20 + "line": 22 } }, { "defaultMessage": "!!!Share as email", "end": { "column": 3, - "line": 27 + "line": 29 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.action.email", "start": { "column": 16, - "line": 24 + "line": 26 } }, { "defaultMessage": "!!!Share on Facebook", "end": { "column": 3, - "line": 31 + "line": 33 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.action.facebook", "start": { "column": 19, - "line": 28 + "line": 30 } }, { "defaultMessage": "!!!Share on Twitter", "end": { "column": 3, - "line": 35 + "line": 37 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.action.twitter", "start": { "column": 18, - "line": 32 + "line": 34 } }, { "defaultMessage": "!!! I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com", "end": { "column": 3, - "line": 39 + "line": 41 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.shareText.email", "start": { "column": 18, - "line": 36 + "line": 38 } }, { "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", "end": { "column": 3, - "line": 43 + "line": 45 }, "file": "src/features/shareFranz/Component.js", "id": "feature.shareFranz.shareText.twitter", "start": { "column": 20, - "line": 40 + "line": 42 } } ], @@ -3816,39 +3873,39 @@ "defaultMessage": "!!!The Franz Todos Preview is currently only available for Franz Premium accounts.", "end": { "column": 3, - "line": 21 + "line": 22 }, "file": "src/features/todos/components/TodosWebview.js", "id": "feature.todos.premium.info", "start": { "column": 15, - "line": 18 + "line": 19 } }, { "defaultMessage": "!!!Upgrade Account", "end": { "column": 3, - "line": 25 + "line": 26 }, "file": "src/features/todos/components/TodosWebview.js", "id": "feature.todos.premium.upgrade", "start": { "column": 14, - "line": 22 + "line": 23 } }, { "defaultMessage": "!!!Franz Todos will be available to everyone soon.", "end": { "column": 3, - "line": 29 + "line": 30 }, "file": "src/features/todos/components/TodosWebview.js", "id": "feature.todos.premium.rollout", "start": { "column": 15, - "line": 26 + "line": 27 } } ], @@ -4127,104 +4184,104 @@ "defaultMessage": "!!!Your workspaces", "end": { "column": 3, - "line": 20 + "line": 22 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.headline", "start": { "column": 12, - "line": 17 + "line": 19 } }, { "defaultMessage": "!!!You haven't added any workspaces yet.", "end": { "column": 3, - "line": 24 + "line": 26 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.noWorkspacesAdded", "start": { "column": 19, - "line": 21 + "line": 23 } }, { "defaultMessage": "!!!Could not load your workspaces", "end": { "column": 3, - "line": 28 + "line": 30 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.workspacesRequestFailed", "start": { "column": 27, - "line": 25 + "line": 27 } }, { "defaultMessage": "!!!Try again", "end": { "column": 3, - "line": 32 + "line": 34 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.tryReloadWorkspaces", "start": { "column": 23, - "line": 29 + "line": 31 } }, { "defaultMessage": "!!!Your changes have been saved", "end": { "column": 3, - "line": 36 + "line": 38 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.updatedInfo", "start": { "column": 15, - "line": 33 + "line": 35 } }, { "defaultMessage": "!!!Workspace has been deleted", "end": { "column": 3, - "line": 40 + "line": 42 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.deletedInfo", "start": { "column": 15, - "line": 37 + "line": 39 } }, { "defaultMessage": "!!!Info about workspace feature", "end": { "column": 3, - "line": 44 + "line": 46 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.workspaceFeatureInfo", "start": { "column": 24, - "line": 41 + "line": 43 } }, { "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", "end": { "column": 3, - "line": 48 + "line": 50 }, "file": "src/features/workspaces/components/WorkspacesDashboard.js", "id": "settings.workspaces.workspaceFeatureHeadline", "start": { "column": 28, - "line": 45 + "line": 47 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 32e9c743a..f11d5ca91 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -5,8 +5,10 @@ "feature.delayApp.headline": "Please purchase a Franz Supporter License to skip waiting", "feature.delayApp.text": "Franz will continue in {seconds} seconds.", "feature.delayApp.trial.action": "Yes, I want the free 14 day trial of Franz Professional", + "feature.delayApp.trial.actionShort": "Activate the free Franz Professional trial", "feature.delayApp.trial.headline": "Get the free Franz Professional 14 day trial and skip the line", "feature.delayApp.upgrade.action": "Get a Franz Supporter License", + "feature.delayApp.upgrade.actionShort": "Upgrade account", "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.", "feature.shareFranz.action.email": "Send as email", "feature.shareFranz.action.facebook": "Share on Facebook", 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 @@ +[ + { + "id": "feature.delayApp.upgrade.action", + "defaultMessage": "!!!Get a Franz Supporter License", + "file": "src/components/ui/ActivateTrialButton/index.js", + "start": { + "line": 13, + "column": 10 + }, + "end": { + "line": 16, + "column": 3 + } + }, + { + "id": "feature.delayApp.trial.action", + "defaultMessage": "!!!Yes, I want the free 14 day trial of Franz Professional", + "file": "src/components/ui/ActivateTrialButton/index.js", + "start": { + "line": 17, + "column": 15 + }, + "end": { + "line": 20, + "column": 3 + } + }, + { + "id": "feature.delayApp.upgrade.actionShort", + "defaultMessage": "!!!Upgrade account", + "file": "src/components/ui/ActivateTrialButton/index.js", + "start": { + "line": 21, + "column": 15 + }, + "end": { + "line": 24, + "column": 3 + } + }, + { + "id": "feature.delayApp.trial.actionShort", + "defaultMessage": "!!!Activate the free Franz Professional trial", + "file": "src/components/ui/ActivateTrialButton/index.js", + "start": { + "line": 25, + "column": 20 + }, + "end": { + "line": 28, + "column": 3 + } + } +] \ 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 @@ "defaultMessage": "!!!Upgrade account", "file": "src/components/ui/PremiumFeatureContainer/index.js", "start": { - "line": 15, + "line": 16, "column": 10 }, "end": { - "line": 18, + "line": 19, "column": 3 } } diff --git a/src/i18n/messages/src/features/todos/components/TodosWebview.json b/src/i18n/messages/src/features/todos/components/TodosWebview.json index 2387112b4..7d26342b7 100644 --- a/src/i18n/messages/src/features/todos/components/TodosWebview.json +++ b/src/i18n/messages/src/features/todos/components/TodosWebview.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!The Franz Todos Preview is currently only available for Franz Premium accounts.", "file": "src/features/todos/components/TodosWebview.js", "start": { - "line": 18, + "line": 19, "column": 15 }, "end": { - "line": 21, + "line": 22, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Upgrade Account", "file": "src/features/todos/components/TodosWebview.js", "start": { - "line": 22, + "line": 23, "column": 14 }, "end": { - "line": 25, + "line": 26, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Franz Todos will be available to everyone soon.", "file": "src/features/todos/components/TodosWebview.js", "start": { - "line": 26, + "line": 27, "column": 15 }, "end": { - "line": 29, + "line": 30, "column": 3 } } 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 @@ "defaultMessage": "!!!Your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 17, + "line": 19, "column": 12 }, "end": { - "line": 20, + "line": 22, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!You haven't added any workspaces yet.", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 21, + "line": 23, "column": 19 }, "end": { - "line": 24, + "line": 26, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Could not load your workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 25, + "line": 27, "column": 27 }, "end": { - "line": 28, + "line": 30, "column": 3 } }, @@ -43,11 +43,11 @@ "defaultMessage": "!!!Try again", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 29, + "line": 31, "column": 23 }, "end": { - "line": 32, + "line": 34, "column": 3 } }, @@ -56,11 +56,11 @@ "defaultMessage": "!!!Your changes have been saved", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 33, + "line": 35, "column": 15 }, "end": { - "line": 36, + "line": 38, "column": 3 } }, @@ -69,11 +69,11 @@ "defaultMessage": "!!!Workspace has been deleted", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 37, + "line": 39, "column": 15 }, "end": { - "line": 40, + "line": 42, "column": 3 } }, @@ -82,11 +82,11 @@ "defaultMessage": "!!!Info about workspace feature", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 41, + "line": 43, "column": 24 }, "end": { - "line": 44, + "line": 46, "column": 3 } }, @@ -95,11 +95,11 @@ "defaultMessage": "!!!Less is More: Introducing Franz Workspaces", "file": "src/features/workspaces/components/WorkspacesDashboard.js", "start": { - "line": 45, + "line": 47, "column": 28 }, "end": { - "line": 48, + "line": 50, "column": 3 } } diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index f3dfbdbf0..7ac7d2375 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -10,6 +10,8 @@ import Request from './lib/Request'; import CachedRequest from './lib/CachedRequest'; import { gaEvent } from '../lib/analytics'; import { sleep } from '../helpers/async-helpers'; +import { getPlan } from '../helpers/plan-helpers'; +import { PLANS } from '../config'; const debug = require('debug')('Franz:UserStore'); @@ -150,10 +152,30 @@ export default class UserStore extends Store { return this.getUserInfoRequest.execute().result || {}; } + @computed get team() { + return this.data.team || null; + } + @computed get isPremium() { return !!this.data.isPremium; } + @computed get isPersonal() { + if (!this.team.plan) return false; + const plan = getPlan(this.team.plan); + + return plan === PLANS.PERSONAL; + } + + @computed get isPro() { + if (!this.team.plan && this.isPremium) return true; + + if (!this.team.plan) return false; + const plan = getPlan(this.team.plan); + + return plan === PLANS.PRO; + } + @computed get legacyServices() { return this.getLegacyServicesRequest.execute() || {}; } -- cgit v1.2.3-70-g09d2 From 24d0223fee38c36ec19d9c662579dba7d787f8b4 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 5 Sep 2019 09:49:25 +0200 Subject: polishing --- src/assets/images/workspaces/teaser_dark.png | Bin 179047 -> 0 bytes src/assets/images/workspaces/teaser_light.png | Bin 182321 -> 0 bytes src/components/settings/team/TeamDashboard.js | 62 ++++++-- src/components/ui/UpgradeButton/index.js | 90 +++++++++++ src/config.js | 2 +- src/containers/settings/TeamScreen.js | 1 + .../subscription/SubscriptionFormScreen.js | 2 +- src/environment.js | 6 +- src/features/todos/components/TodosWebview.js | 8 +- src/features/todos/store.js | 18 ++- .../workspaces/components/WorkspacesDashboard.js | 167 +++++++++++---------- .../workspaces/containers/WorkspacesScreen.js | 13 +- src/i18n/globalMessages.js | 4 + src/i18n/locales/defaultMessages.json | 67 ++++++--- src/i18n/locales/en-US.json | 4 +- .../components/settings/team/TeamDashboard.json | 24 +-- .../src/components/ui/UpgradeButton/index.json | 15 ++ src/i18n/messages/src/i18n/globalMessages.json | 13 ++ src/stores/UIStore.js | 1 - src/stores/UserStore.js | 4 +- 20 files changed, 360 insertions(+), 141 deletions(-) delete mode 100644 src/assets/images/workspaces/teaser_dark.png delete mode 100644 src/assets/images/workspaces/teaser_light.png create mode 100644 src/components/ui/UpgradeButton/index.js create mode 100644 src/i18n/messages/src/components/ui/UpgradeButton/index.json (limited to 'src/components') diff --git a/src/assets/images/workspaces/teaser_dark.png b/src/assets/images/workspaces/teaser_dark.png deleted file mode 100644 index 5b6d7334b..000000000 Binary files a/src/assets/images/workspaces/teaser_dark.png and /dev/null differ diff --git a/src/assets/images/workspaces/teaser_light.png b/src/assets/images/workspaces/teaser_light.png deleted file mode 100644 index 635af43fa..000000000 Binary files a/src/assets/images/workspaces/teaser_light.png and /dev/null differ diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js index 990ee52e7..51a3f375d 100644 --- a/src/components/settings/team/TeamDashboard.js +++ b/src/components/settings/team/TeamDashboard.js @@ -5,10 +5,14 @@ import { defineMessages, intlShape } from 'react-intl'; import ReactTooltip from 'react-tooltip'; import injectSheet from 'react-jss'; +import { Badge } from '@meetfranz/ui'; import Loader from '../../ui/Loader'; import Button from '../../ui/Button'; import Infobox from '../../ui/Infobox'; import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer'; +import globalMessages from '../../../i18n/globalMessages'; +import ActivateTrialButton from '../../ui/ActivateTrialButton'; +import UpgradeButton from '../../ui/UpgradeButton'; const messages = defineMessages({ headline: { @@ -40,6 +44,7 @@ const messages = defineMessages({ const styles = { cta: { margin: [40, 'auto'], + height: 'auto', }, container: { display: 'flex', @@ -69,6 +74,17 @@ const styles = { order: 1, }, }, + headline: { + marginBottom: 0, + }, + proRequired: { + margin: [10, 0, 40], + height: 'auto', + }, + buttonContainer: { + display: 'flex', + height: 'auto', + }, }; @@ -79,6 +95,7 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon retryUserInfoRequest: PropTypes.func.isRequired, openTeamManagement: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, + isProUser: PropTypes.bool.isRequired, }; static contextTypes = { @@ -91,6 +108,7 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon userInfoRequestFailed, retryUserInfoRequest, openTeamManagement, + isProUser, classes, } = this.props; const { intl } = this.context; @@ -123,23 +141,35 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon <> {!isLoading && ( <> - - <> -

{intl.formatMessage(messages.contentHeadline)}

-
-
-

{intl.formatMessage(messages.intro)}

-

{intl.formatMessage(messages.copy)}

-
- Franz for Teams + <> +

{intl.formatMessage(messages.contentHeadline)}

+ {!isProUser && ( + {intl.formatMessage(globalMessages.proRequired)} + )} +
+
+

{intl.formatMessage(messages.intro)}

+

{intl.formatMessage(messages.copy)}

- - -
+
+ {!isProUser ? ( + + ) : ( +
+ )} diff --git a/src/components/ui/UpgradeButton/index.js b/src/components/ui/UpgradeButton/index.js new file mode 100644 index 000000000..4aa494e38 --- /dev/null +++ b/src/components/ui/UpgradeButton/index.js @@ -0,0 +1,90 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { inject, observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import classnames from 'classnames'; + +import { Button } from '@meetfranz/forms'; +import { gaEvent } from '../../../lib/analytics'; + +import UserStore from '../../../stores/UserStore'; +import ActivateTrialButton from '../ActivateTrialButton'; + +const messages = defineMessages({ + upgradeToPro: { + id: 'global.upgradeButton.upgradeToPro', + defaultMessage: '!!!Upgrade to Franz Professional', + }, +}); + +@inject('stores', 'actions') @observer +class UpgradeButton extends Component { + static propTypes = { + // eslint-disable-next-line + classes: PropTypes.object.isRequired, + className: PropTypes.string, + gaEventInfo: PropTypes.shape({ + category: PropTypes.string.isRequired, + event: PropTypes.string.isRequired, + label: PropTypes.string, + }), + requiresPro: PropTypes.bool, + }; + + static defaultProps = { + className: '', + gaEventInfo: null, + requiresPro: false, + } + + static contextTypes = { + intl: intlShape, + }; + + handleCTAClick() { + const { actions, gaEventInfo } = this.props; + + actions.ui.openSettings({ path: 'user' }); + if (gaEventInfo) { + const { category, event } = gaEventInfo; + gaEvent(category, event, 'Upgrade Account'); + } + } + + render() { + const { stores, requiresPro } = this.props; + const { intl } = this.context; + + const { isPremium, isPersonal } = stores.user; + + if (isPremium && isPersonal && requiresPro) { + return ( +