From 65b0912c4595b0b3cfad0f1d19255f70ba2bc48a Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Mon, 14 Oct 2019 13:59:48 +0200 Subject: Move checkout to in app instead of external handling --- src/components/settings/account/AccountDashboard.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/components/settings') diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index ac2594604..08e86fda6 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js @@ -109,6 +109,7 @@ class AccountDashboard extends Component { openBilling: PropTypes.func.isRequired, upgradeToPro: PropTypes.func.isRequired, openInvoices: PropTypes.func.isRequired, + onCloseSubscriptionWindow: PropTypes.func.isRequired, }; static contextTypes = { @@ -130,6 +131,7 @@ class AccountDashboard extends Component { openBilling, upgradeToPro, openInvoices, + onCloseSubscriptionWindow, } = this.props; const { intl } = this.context; @@ -263,7 +265,9 @@ class AccountDashboard extends Component { {!user.isPremium && (
- +
)} -- cgit v1.2.3-70-g09d2 From 91a0fb20ef02dfa342cf26df3e047b2bd4370b9f Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Tue, 15 Oct 2019 21:40:14 +0200 Subject: simplify plan selection --- packages/theme/src/themes/dark/index.ts | 4 +- src/actions/index.js | 2 + src/components/auth/Pricing.js | 17 +- src/components/layout/AppLayout.js | 2 + .../settings/account/AccountDashboard.js | 3 +- src/components/ui/FeatureItem.js | 1 + src/components/ui/FeatureList.js | 73 +++- src/config.js | 8 +- src/containers/auth/PricingScreen.js | 3 +- src/features/planSelection/actions.js | 13 + src/features/planSelection/api.js | 26 ++ src/features/planSelection/components/PlanItem.js | 186 ++++++++++ .../planSelection/components/PlanSelection.js | 233 ++++++++++++ .../containers/PlanSelectionScreen.js | 138 +++++++ src/features/planSelection/index.js | 30 ++ src/features/planSelection/store.js | 113 ++++++ src/features/shareFranz/index.js | 3 +- src/helpers/plan-helpers.js | 8 +- src/i18n/locales/defaultMessages.json | 396 +++++++++++++++++++-- src/i18n/locales/en-US.json | 46 ++- src/i18n/messages/src/components/auth/Pricing.json | 4 +- .../messages/src/components/layout/AppLayout.json | 12 +- .../messages/src/components/ui/FeatureList.json | 105 ++++-- .../planSelection/components/PlanItem.json | 28 ++ .../planSelection/components/PlanSelection.json | 145 ++++++++ .../planSelection/components/PlanTeaser.json | 28 ++ .../containers/PlanSelectionScreen.json | 54 +++ src/i18n/messages/src/helpers/plan-helpers.json | 8 +- src/stores/FeaturesStore.js | 2 + src/stores/index.js | 2 + 30 files changed, 1589 insertions(+), 104 deletions(-) create mode 100644 src/features/planSelection/actions.js create mode 100644 src/features/planSelection/api.js create mode 100644 src/features/planSelection/components/PlanItem.js create mode 100644 src/features/planSelection/components/PlanSelection.js create mode 100644 src/features/planSelection/containers/PlanSelectionScreen.js create mode 100644 src/features/planSelection/index.js create mode 100644 src/features/planSelection/store.js create mode 100644 src/i18n/messages/src/features/planSelection/components/PlanItem.json create mode 100644 src/i18n/messages/src/features/planSelection/components/PlanSelection.json create mode 100644 src/i18n/messages/src/features/planSelection/components/PlanTeaser.json create mode 100644 src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json (limited to 'src/components/settings') diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index 67d0cfb71..9a66f3463 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts @@ -65,8 +65,8 @@ export const selectOptionItemHoverColor = selectColor; export const selectSearchColor = inputBackground; // Modal -export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); -export const colorModalBackground = colorContentBackground; +export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.5).rgb().string(); +export const colorModalBackground = legacyStyles.darkThemeGrayDark; // Services export const services = merge({}, defaultStyles.services, { diff --git a/src/actions/index.js b/src/actions/index.js index 336344d76..1c033fb96 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -14,6 +14,7 @@ import requests from './requests'; import announcements from '../features/announcements/actions'; import workspaces from '../features/workspaces/actions'; import todos from '../features/todos/actions'; +import planSelection from '../features/planSelection/actions'; const actions = Object.assign({}, { service, @@ -33,4 +34,5 @@ export default Object.assign( { announcements }, { workspaces }, { todos }, + { planSelection }, ); diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index cbeaaa5d9..40ce49814 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js @@ -18,7 +18,7 @@ const messages = defineMessages({ }, personalOffer: { id: 'pricing.trial.subheadline', - defaultMessage: '!!!Your personal welcome offer:', + defaultMessage: '!!!Here\'s a special welcome for you:', }, noStringsAttachedHeadline: { id: 'pricing.trial.terms.headline', @@ -38,7 +38,7 @@ const messages = defineMessages({ }, ctaAccept: { id: 'pricing.trial.cta.accept', - defaultMessage: '!!!Yes, upgrade my account to Franz Professional', + defaultMessage: '!!!Start my 14-day Franz Professional Trial ', }, ctaSkip: { id: 'pricing.trial.cta.skip', @@ -58,6 +58,7 @@ const styles = theme => ({ welcomeOffer: { textAlign: 'center', fontWeight: 'bold', + marginBottom: '6 !important', }, keyTerms: { textAlign: 'center', @@ -101,6 +102,7 @@ export default @observer @injectSheet(styles) class Signup extends Component { isLoadingRequiredData: PropTypes.bool.isRequired, isActivatingTrial: PropTypes.bool.isRequired, trialActivationError: PropTypes.bool.isRequired, + canSkipTrial: PropTypes.bool.isRequired, classes: PropTypes.object.isRequired, }; @@ -114,6 +116,7 @@ export default @observer @injectSheet(styles) class Signup extends Component { isLoadingRequiredData, isActivatingTrial, trialActivationError, + canSkipTrial, classes, } = this.props; const { intl } = this.context; @@ -138,7 +141,7 @@ export default @observer @injectSheet(styles) class Signup extends Component {

- Get the free 14 day Franz Professional trial and see your communication evolving. + For the next 14 days, we are going to give you the full Franz Professional experience so you can watch your communication evolve!

@@ -167,9 +170,11 @@ export default @observer @injectSheet(styles) class Signup extends Component { busy={isActivatingTrial} disabled={isLoadingRequiredData || isActivatingTrial} /> -

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

+ {canSkipTrial && ( +

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

+ )}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index 200777ae6..fe81b1911 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -19,6 +19,7 @@ import { workspaceStore } from '../../features/workspaces'; import AppUpdateInfoBar from '../AppUpdateInfoBar'; import TrialActivationInfoBar from '../TrialActivationInfoBar'; import Todos from '../../features/todos/containers/TodosScreen'; +import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -176,6 +177,7 @@ class AppLayout extends Component {
+ ); diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 08e86fda6..9a1b31d0f 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js @@ -217,7 +217,8 @@ class AccountDashboard extends Component { {intl.formatMessage(messages.yourLicense)}

- {isPremiumOverrideUser ? 'Franz Premium' : planName} + Franz + {isPremiumOverrideUser ? 'Premium' : planName} {user.team.isTrial && ( <> {' – '} diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js index 7c482c4d4..4926df470 100644 --- a/src/components/ui/FeatureItem.js +++ b/src/components/ui/FeatureItem.js @@ -10,6 +10,7 @@ const styles = theme => ({ padding: [8, 0], display: 'flex', alignItems: 'center', + textAlign: 'left', }, featureIcon: { fill: theme.brandSuccess, diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js index 62944ad75..732b40e40 100644 --- a/src/components/ui/FeatureList.js +++ b/src/components/ui/FeatureList.js @@ -3,12 +3,33 @@ import PropTypes from 'prop-types'; import { defineMessages, intlShape } from 'react-intl'; import { FeatureItem } from './FeatureItem'; +import { PLANS } from '../../config'; const messages = defineMessages({ + availableRecipes: { + id: 'pricing.features.recipes', + defaultMessage: '!!!Choose from more than 70 Services', + }, + accountSync: { + id: 'pricing.features.accountSync', + defaultMessage: '!!!Account Synchronisation', + }, + desktopNotifications: { + id: 'pricing.features.desktopNotifications', + defaultMessage: '!!!Desktop Notifications', + }, unlimitedServices: { id: 'pricing.features.unlimitedServices', defaultMessage: '!!!Add unlimited services', }, + upToThreeServices: { + id: 'pricing.features.upToThreeServices', + defaultMessage: '!!!Add up to 3 services', + }, + upToSixServices: { + id: 'pricing.features.upToSixServices', + defaultMessage: '!!!Add up to 6 services', + }, spellchecker: { id: 'pricing.features.spellchecker', defaultMessage: '!!!Spellchecker support', @@ -51,6 +72,7 @@ export class FeatureList extends Component { static propTypes = { className: PropTypes.string, featureClassName: PropTypes.string, + plan: PropTypes.oneOf(PLANS).isRequired, }; static defaultProps = { @@ -66,21 +88,52 @@ export class FeatureList extends Component { const { className, featureClassName, + plan, } = this.props; const { intl } = this.context; + const features = []; + if (plan === PLANS.FREE) { + features.push( + messages.upToThreeServices, + messages.availableRecipes, + messages.accountSync, + messages.desktopNotifications, + ); + } else if (plan === PLANS.PERSONAL) { + features.push( + messages.upToSixServices, + messages.spellchecker, + messages.appDelays, + messages.adFree, + ); + } else if (plan === PLANS.PRO) { + features.push( + messages.unlimitedServices, + messages.workspaces, + messages.customWebsites, + // messages.onPremise, + messages.thirdPartyServices, + // messages.serviceProxies, + ); + } else { + features.push( + messages.unlimitedServices, + messages.spellchecker, + messages.workspaces, + messages.customWebsites, + messages.onPremise, + messages.thirdPartyServices, + messages.serviceProxies, + messages.teamManagement, + messages.appDelays, + messages.adFree, + ); + } + return (

    - - - - - - - - - - + {features.map(feature => )}
); } diff --git a/src/config.js b/src/config.js index 78a92d948..11e6cb91f 100644 --- a/src/config.js +++ b/src/config.js @@ -91,10 +91,10 @@ export const ALLOWED_PROTOCOLS = [ ]; export const PLANS = { - PERSONAL: 'PERSONAL', - PRO: 'PRO', - LEGACY: 'LEGACY', - FREE: 'FREE', + PERSONAL: 'personal', + PRO: 'pro', + LEGACY: 'legacy', + FREE: 'free', }; export const PLANS_MAPPING = { diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index af1651931..8e0ded16a 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js @@ -38,7 +38,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend } = this.props; const { getUserInfoRequest, activateTrialRequest } = stores.user; - const { featuresRequest } = stores.features; + const { featuresRequest, features } = stores.features; return ( ); diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js new file mode 100644 index 000000000..21aa38ace --- /dev/null +++ b/src/features/planSelection/actions.js @@ -0,0 +1,13 @@ +import PropTypes from 'prop-types'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; + +export const planSelectionActions = createActionsFromDefinitions({ + upgradeAccount: { + planId: PropTypes.string.isRequired, + onCloseWindow: PropTypes.func.isRequired, + }, + downgradeAccount: {}, + hideOverlay: {}, +}, PropTypes.checkPropTypes); + +export default planSelectionActions; diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js new file mode 100644 index 000000000..734643f10 --- /dev/null +++ b/src/features/planSelection/api.js @@ -0,0 +1,26 @@ +import { sendAuthRequest } from '../../api/utils/auth'; +import { API, API_VERSION } from '../../environment'; +import Request from '../../stores/lib/Request'; + +const debug = require('debug')('Franz:feature:planSelection:api'); + +export const planSelectionApi = { + downgrade: async () => { + const url = `${API}/${API_VERSION}/payment/downgrade`; + const options = { + method: 'PUT', + }; + debug('downgrade UPDATE', url, options); + const result = await sendAuthRequest(url, options); + debug('downgrade RESULT', result); + if (!result.ok) throw result; + + return result.ok; + }, +}; + +export const downgradeUserRequest = new Request(planSelectionApi, 'downgrade'); + +export const resetApiRequests = () => { + downgradeUserRequest.reset(); +}; diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js new file mode 100644 index 000000000..a49cd40d3 --- /dev/null +++ b/src/features/planSelection/components/PlanItem.js @@ -0,0 +1,186 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import { defineMessages, intlShape } from 'react-intl'; +import injectSheet from 'react-jss'; +import classnames from 'classnames'; +import color from 'color'; + +import { H2 } from '@meetfranz/ui'; + +import { Button } from '@meetfranz/forms'; +import { mdiArrowRight } from '@mdi/js'; +// import { FeatureList } from '../ui/FeatureList'; +// import { PLANS, PAYMENT_INTERVAL } from '../../config'; +// import { i18nPlanName, i18nIntervalName } from '../../helpers/plan-helpers'; +// import { PLAN_INTERVAL_CONFIG_TYPE } from './types'; + +const messages = defineMessages({ + perMonth: { + id: 'subscription.interval.perMonth', + defaultMessage: '!!!per month', + }, + perMonthPerUser: { + id: 'subscription.interval.perMonthPerUser', + defaultMessage: '!!!per month & user', + }, +}); + +const styles = theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + borderRadius: theme.borderRadius, + flex: 1, + color: theme.styleTypes.primary.accent, + overflow: 'hidden', + textAlign: 'center', + + '& h2': { + textAlign: 'center', + marginBottom: 20, + fontSize: 30, + color: theme.styleTypes.primary.contrast, + // fontWeight: 'bold', + }, + }, + currency: { + fontSize: 35, + }, + priceWrapper: { + height: 50, + }, + price: { + fontSize: 50, + + '& sup': { + fontSize: 20, + verticalAlign: 20, + }, + }, + interval: { + // paddingBottom: 40, + }, + text: { + marginBottom: 'auto', + }, + cta: { + background: theme.styleTypes.primary.accent, + color: theme.styleTypes.primary.contrast, + margin: [40, 'auto', 0, 'auto'], + + // '&:active': { + // opacity: 0.7, + // }, + }, + divider: { + width: 40, + border: 0, + borderTop: [1, 'solid', theme.styleTypes.primary.contrast], + margin: [30, 'auto'], + }, + header: { + padding: 20, + background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), + color: theme.styleTypes.primary.contrast, + }, + content: { + padding: 20, + // border: [1, 'solid', 'red'], + background: '#EFEFEF', + }, + simpleCTA: { + background: 'none', + color: theme.styleTypes.primary.accent, + + '& svg': { + fill: theme.styleTypes.primary.accent, + }, + }, +}); + + +export default @observer @injectSheet(styles) class PlanItem extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + text: PropTypes.string.isRequired, + price: PropTypes.number.isRequired, + currency: PropTypes.string.isRequired, + upgrade: PropTypes.func.isRequired, + ctaLabel: PropTypes.string.isRequired, + simpleCTA: PropTypes.bool, + perUser: PropTypes.bool, + classes: PropTypes.object.isRequired, + children: PropTypes.element, + }; + + static defaultProps = { + simpleCTA: false, + perUser: false, + children: null, + } + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { + name, + text, + price, + currency, + classes, + upgrade, + ctaLabel, + simpleCTA, + perUser, + children, + } = this.props; + const { intl } = this.context; + + const priceParts = `${price}`.split('.'); + // const intervalName = i18nIntervalName(PAYMENT_INTERVAL.MONTHLY, intl); + + return ( +
+
+

{name}

+

+ {text} +

+
+

+ {currency} + + {priceParts[0]} + {priceParts[1]} + +

+

+ {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)} +

+
+ +
+ {children} + +
+ +
+ ); + } +} diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js new file mode 100644 index 000000000..84d2d9e89 --- /dev/null +++ b/src/features/planSelection/components/PlanSelection.js @@ -0,0 +1,233 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import { defineMessages, intlShape } from 'react-intl'; +import { H1, H2, Icon } from '@meetfranz/ui'; +import color from 'color'; + +import { mdiRocket } from '@mdi/js'; +import PlanItem from './PlanItem'; +import { i18nPlanName } from '../../../helpers/plan-helpers'; +import { PLANS } from '../../../config'; +import { FeatureList } from '../../../components/ui/FeatureList'; + +const messages = defineMessages({ + welcome: { + id: 'feature.planSelection.fullscreen.welcome', + defaultMessage: '!!!Welcome back, {name}', + }, + subheadline: { + id: 'feature.planSelection.fullscreen.subheadline', + defaultMessage: '!!!It\'s time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.', + }, + textFree: { + id: 'feature.planSelection.free.text', + defaultMessage: '!!!Basic functionality', + }, + textPersonal: { + id: 'feature.planSelection.personal.text', + defaultMessage: '!!!More services, no waiting - ideal for personal use.', + }, + textProfessional: { + id: 'feature.planSelection.pro.text', + defaultMessage: '!!!Unlimited services and professional features for you - and your team.', + }, + ctaStayOnFree: { + id: 'feature.planSelection.cta.stayOnFree', + defaultMessage: '!!!Stay on Free', + }, + ctaDowngradeFree: { + id: 'feature.planSelection.cta.ctaDowngradeFree', + defaultMessage: '!!!Downgrade to Free', + }, + actionTrial: { + id: 'feature.planSelection.cta.trial', + defaultMessage: '!!!Start my free 14-days Trial', + }, + shortActionPersonal: { + id: 'feature.planSelection.cta.upgradePersonal', + defaultMessage: '!!!Choose Personal', + }, + shortActionPro: { + id: 'feature.planSelection.cta.upgradePro', + defaultMessage: '!!!Choose Professional', + }, + fullFeatureList: { + id: 'feature.planSelection.fullFeatureList', + defaultMessage: '!!!Complete comparison of all plans', + }, +}); + +const styles = theme => ({ + root: { + background: theme.colorModalOverlayBackground, + width: '100%', + height: '100%', + position: 'absolute', + top: 0, + left: 0, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 999999, + }, + container: { + width: '80%', + height: 'auto', + background: theme.styleTypes.primary.accent, + padding: 40, + borderRadius: theme.borderRadius, + maxWidth: 1000, + + '& h1, & h2': { + textAlign: 'center', + }, + }, + plans: { + display: 'flex', + margin: [40, 0, 0], + height: 'auto', + + '& > div': { + margin: [0, 15], + flex: 1, + height: 'auto', + background: theme.styleTypes.primary.contrast, + boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()], + }, + }, + bigIcon: { + background: theme.styleTypes.danger.accent, + width: 120, + height: 120, + display: 'flex', + alignItems: 'center', + borderRadius: '100%', + justifyContent: 'center', + margin: [-100, 'auto', 20], + + '& svg': { + width: '80px !important', + }, + }, + headline: { + fontSize: 40, + }, + subheadline: { + maxWidth: 660, + fontSize: 22, + lineHeight: 1.1, + margin: [0, 'auto'], + }, + featureList: { + '& li': { + borderBottom: [1, 'solid', '#CECECE'], + }, + }, + fullFeatureList: { + marginTop: 40, + textAlign: 'center', + display: 'block', + color: `${theme.styleTypes.primary.contrast} !important`, + }, +}); + +@injectSheet(styles) @observer +class PlanSelection extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + firstname: PropTypes.string.isRequired, + plans: PropTypes.object.isRequired, + currency: PropTypes.string.isRequired, + subscriptionExpired: PropTypes.bool.isRequired, + upgradeAccount: PropTypes.func.isRequired, + stayOnFree: PropTypes.func.isRequired, + hadSubscription: PropTypes.bool.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { + classes, + firstname, + plans, + currency, + subscriptionExpired, + upgradeAccount, + stayOnFree, + hadSubscription, + } = this.props; + + const { intl } = this.context; + + return ( +
+
+
+ +
+

{intl.formatMessage(messages.welcome, { name: firstname })}

+

{intl.formatMessage(messages.subheadline)}

+
+ stayOnFree()} + simpleCTA + > + + + upgradeAccount(plans.personal.yearly.id)} + > + + + upgradeAccount(plans.personal.yearly.id)} + perUser + > + + +
+ + {intl.formatMessage(messages.fullFeatureList)} + +
+
+ ); + } +} + +export default PlanSelection; diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js new file mode 100644 index 000000000..b0d9b5ab5 --- /dev/null +++ b/src/features/planSelection/containers/PlanSelectionScreen.js @@ -0,0 +1,138 @@ +import React, { Component } from 'react'; +import { observer, inject } from 'mobx-react'; +import PropTypes from 'prop-types'; +import { remote } from 'electron'; +import { defineMessages, intlShape } from 'react-intl'; + +import FeaturesStore from '../../../stores/FeaturesStore'; +import UserStore from '../../../stores/UserStore'; +import PlanSelection from '../components/PlanSelection'; +import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import { planSelectionStore } from '..'; + +const { dialog, app } = remote; + +const messages = defineMessages({ + dialogTitle: { + id: 'feature.planSelection.fullscreen.dialog.title', + defaultMessage: '!!!Downgrade your Franz Plan', + }, + dialogMessage: { + id: 'feature.planSelection.fullscreen.dialog.message', + defaultMessage: '!!!You\'re about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.', + }, + dialogCTADowngrade: { + id: 'feature.planSelection.fullscreen.dialog.cta.downgrade', + defaultMessage: '!!!Downgrade to Free', + }, + dialogCTAUpgrade: { + id: 'feature.planSelection.fullscreen.dialog.cta.upgrade', + defaultMessage: '!!!Choose Personal', + }, +}); + +@inject('stores', 'actions') @observer +class PlanSelectionScreen extends Component { + static contextTypes = { + intl: intlShape, + }; + + upgradeAccount(planId) { + const { user, features } = this.props.stores; + const { upgradeAccount, hideOverlay } = this.props.actions.planSelection; + + upgradeAccount({ + planId, + onCloseWindow: () => { + hideOverlay(); + user.getUserInfoRequest.invalidate({ immediately: true }); + features.featuresRequest.invalidate({ immediately: true }); + }, + }); + } + + render() { + if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) { + return null; + } + + const { intl } = this.context; + + const { user, features } = this.props.stores; + const { plans, currency } = features.features.pricingConfig; + const { activateTrial } = this.props.actions.user; + const { upgradeAccount, downgradeAccount, hideOverlay } = this.props.actions.planSelection; + + // const planConfig = [{ + // id: 'free', + // price: 0, + // }, { + // id: plans.personal.yearly.id, + // price: plans.personal.yearly.price, + // }, { + // id: plans.pro.yearly.id, + // price: plans.pro.yearly.price, + // }]; + + return ( + + { + if (user.data.hadSubscription) { + this.upgradeAccount(planId); + } else { + activateTrial({ + planId, + }); + } + }} + stayOnFree={() => { + const selection = dialog.showMessageBoxSync(app.mainWindow, { + type: 'question', + message: intl.formatMessage(messages.dialogTitle), + detail: intl.formatMessage(messages.dialogMessage, { + currency, + price: plans.personal.yearly.price, + }), + buttons: [ + intl.formatMessage(messages.dialogCTADowngrade), + intl.formatMessage(messages.dialogCTAUpgrade), + ], + }); + + if (selection === 0) { + downgradeAccount(); + hideOverlay(); + } else { + upgradeAccount(plans.personal.yearly.id); + } + }} + subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded} + hadSubscription={user.data.hadSubscription} + /> + + ); + } +} + +export default PlanSelectionScreen; + +PlanSelectionScreen.wrappedComponent.propTypes = { + stores: PropTypes.shape({ + features: PropTypes.instanceOf(FeaturesStore).isRequired, + user: PropTypes.instanceOf(UserStore).isRequired, + }).isRequired, + actions: PropTypes.shape({ + planSelection: PropTypes.shape({ + upgradeAccount: PropTypes.func.isRequired, + downgradeAccount: PropTypes.func.isRequired, + hideOverlay: PropTypes.func.isRequired, + }), + user: PropTypes.shape({ + activateTrial: PropTypes.func.isRequired, + }), + }).isRequired, +}; diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js new file mode 100644 index 000000000..81189207a --- /dev/null +++ b/src/features/planSelection/index.js @@ -0,0 +1,30 @@ +import { reaction } from 'mobx'; +import PlanSelectionStore from './store'; + +const debug = require('debug')('Franz:feature:planSelection'); + +export const GA_CATEGORY_PLAN_SELECTION = 'planSelection'; + +export const planSelectionStore = new PlanSelectionStore(); + +export default function initPlanSelection(stores, actions) { + stores.planSelection = planSelectionStore; + const { features } = stores; + + // Toggle planSelection feature + reaction( + () => features.features.isPlanSelectionEnabled, + (isEnabled) => { + if (isEnabled) { + debug('Initializing `planSelection` feature'); + planSelectionStore.start(stores, actions); + } else if (planSelectionStore.isFeatureActive) { + debug('Disabling `planSelection` feature'); + planSelectionStore.stop(); + } + }, + { + fireImmediately: true, + }, + ); +} diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js new file mode 100644 index 000000000..50e46dfb3 --- /dev/null +++ b/src/features/planSelection/store.js @@ -0,0 +1,113 @@ +import { + action, + observable, + computed, +} from 'mobx'; +import { remote } from 'electron'; + +import { planSelectionActions } from './actions'; +import { FeatureStore } from '../utils/FeatureStore'; +// import { createReactions } from '../../stores/lib/Reaction'; +import { createActionBindings } from '../utils/ActionBinding'; +import { downgradeUserRequest } from './api'; + +const debug = require('debug')('Franz:feature:planSelection:store'); + +const { BrowserWindow } = remote; + +export default class PlanSelectionStore extends FeatureStore { + @observable isFeatureEnabled = false; + + @observable isFeatureActive = false; + + @observable hideOverlay = false; + + @computed get showPlanSelectionOverlay() { + const { team } = this.stores.user; + if (team && !this.hideOverlay) { + return team.state === 'expired' && !team.userHasDowngraded; + } + + return false; + } + + // ========== PUBLIC API ========= // + + @action start(stores, actions, api) { + debug('PlanSelectionStore::start'); + this.stores = stores; + this.actions = actions; + this.api = api; + + // ACTIONS + + this._registerActions(createActionBindings([ + [planSelectionActions.upgradeAccount, this._upgradeAccount], + [planSelectionActions.downgradeAccount, this._downgradeAccount], + [planSelectionActions.hideOverlay, this._hideOverlay], + ])); + + // REACTIONS + + // this._allReactions = createReactions([ + // this._setFeatureEnabledReaction, + // this._updateTodosConfig, + // this._firstLaunchReaction, + // this._routeCheckReaction, + // ]); + + // this._registerReactions(this._allReactions); + + this.isFeatureActive = true; + } + + @action stop() { + super.stop(); + debug('PlanSelectionStore::stop'); + this.reset(); + this.isFeatureActive = false; + } + + // ========== PRIVATE METHODS ========= // + + // Actions + + @action _upgradeAccount = ({ planId, onCloseWindow = () => null }) => { + let hostedPageURL = this.stores.features.features.subscribeURL; + + const parsedUrl = new URL(hostedPageURL); + const params = new URLSearchParams(parsedUrl.search.slice(1)); + + params.set('plan', planId); + + hostedPageURL = this.stores.user.getAuthURL(`${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`); + + const win = new BrowserWindow({ + parent: remote.getCurrentWindow(), + modal: true, + title: '🔒 Upgrade Your Franz Account', + width: 800, + height: window.innerHeight - 100, + maxWidth: 800, + minWidth: 600, + webPreferences: { + nodeIntegration: true, + webviewTag: true, + }, + }); + win.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPageURL)}`); + + win.on('closed', () => { + onCloseWindow(); + }); + }; + + @action _downgradeAccount = () => { + console.log('downgrade to free', downgradeUserRequest); + downgradeUserRequest.execute(); + } + + @action _hideOverlay = () => { + this.hideOverlay = true; + } +} diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js index 87deacef4..a39d7a6e6 100644 --- a/src/features/shareFranz/index.js +++ b/src/features/shareFranz/index.js @@ -3,6 +3,7 @@ import ms from 'ms'; import { state as delayAppState } from '../delayApp'; import { gaEvent, gaPage } from '../../lib/analytics'; +import { planSelectionStore } from '../planSelection'; export { default as Component } from './Component'; @@ -35,7 +36,7 @@ export default function initialize(stores) { () => stores.user.isLoggedIn, () => { setTimeout(() => { - if (stores.settings.stats.appStarts % 50 === 0) { + if (stores.settings.stats.appStarts % 50 === 0 && !planSelectionStore.showPlanSelectionOverlay) { if (delayAppState.isDelayAppScreenVisible) { debug('Delaying share modal by 5 minutes'); setTimeout(() => showModal(), ms('5m')); diff --git a/src/helpers/plan-helpers.js b/src/helpers/plan-helpers.js index e0f1fd89a..ee22e4471 100644 --- a/src/helpers/plan-helpers.js +++ b/src/helpers/plan-helpers.js @@ -4,19 +4,19 @@ import { PLANS_MAPPING, PLANS } from '../config'; const messages = defineMessages({ [PLANS.PRO]: { id: 'pricing.plan.pro', - defaultMessage: '!!!Franz Professional', + defaultMessage: '!!!Professional', }, [PLANS.PERSONAL]: { id: 'pricing.plan.personal', - defaultMessage: '!!!Franz Personal', + defaultMessage: '!!!Personal', }, [PLANS.FREE]: { id: 'pricing.plan.free', - defaultMessage: '!!!Franz Free', + defaultMessage: '!!!Free', }, [PLANS.LEGACY]: { id: 'pricing.plan.legacy', - defaultMessage: '!!!Franz Premium', + defaultMessage: '!!!Premium', }, }); diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 703f800f9..210ea001a 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -430,7 +430,7 @@ } }, { - "defaultMessage": "!!!Your personal welcome offer:", + "defaultMessage": "!!!Here's a special welcome for you:", "end": { "column": 3, "line": 22 @@ -495,7 +495,7 @@ } }, { - "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", + "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "end": { "column": 3, "line": 42 @@ -721,39 +721,39 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 31 + "line": 32 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 28 + "line": 29 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 35 + "line": 36 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 32 + "line": 33 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 39 + "line": 40 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 36 + "line": 37 } } ], @@ -3016,134 +3016,199 @@ }, { "descriptors": [ + { + "defaultMessage": "!!!Choose from more than 70 Services", + "end": { + "column": 3, + "line": 12 + }, + "file": "src/components/ui/FeatureList.js", + "id": "pricing.features.recipes", + "start": { + "column": 20, + "line": 9 + } + }, + { + "defaultMessage": "!!!Account Synchronisation", + "end": { + "column": 3, + "line": 16 + }, + "file": "src/components/ui/FeatureList.js", + "id": "pricing.features.accountSync", + "start": { + "column": 15, + "line": 13 + } + }, + { + "defaultMessage": "!!!Desktop Notifications", + "end": { + "column": 3, + "line": 20 + }, + "file": "src/components/ui/FeatureList.js", + "id": "pricing.features.desktopNotifications", + "start": { + "column": 24, + "line": 17 + } + }, { "defaultMessage": "!!!Add unlimited services", "end": { "column": 3, - "line": 11 + "line": 24 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.unlimitedServices", "start": { "column": 21, - "line": 8 + "line": 21 + } + }, + { + "defaultMessage": "!!!Add up to 3 services", + "end": { + "column": 3, + "line": 28 + }, + "file": "src/components/ui/FeatureList.js", + "id": "pricing.features.upToThreeServices", + "start": { + "column": 21, + "line": 25 + } + }, + { + "defaultMessage": "!!!Add up to 6 services", + "end": { + "column": 3, + "line": 32 + }, + "file": "src/components/ui/FeatureList.js", + "id": "pricing.features.upToSixServices", + "start": { + "column": 19, + "line": 29 } }, { "defaultMessage": "!!!Spellchecker support", "end": { "column": 3, - "line": 15 + "line": 36 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.spellchecker", "start": { "column": 16, - "line": 12 + "line": 33 } }, { "defaultMessage": "!!!Workspaces", "end": { "column": 3, - "line": 19 + "line": 40 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.workspaces", "start": { "column": 14, - "line": 16 + "line": 37 } }, { "defaultMessage": "!!!Add Custom Websites", "end": { "column": 3, - "line": 23 + "line": 44 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.customWebsites", "start": { "column": 18, - "line": 20 + "line": 41 } }, { "defaultMessage": "!!!On-premise & other Hosted Services", "end": { "column": 3, - "line": 27 + "line": 48 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.onPremise", "start": { "column": 13, - "line": 24 + "line": 45 } }, { "defaultMessage": "!!!Install 3rd party services", "end": { "column": 3, - "line": 31 + "line": 52 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.thirdPartyServices", "start": { "column": 22, - "line": 28 + "line": 49 } }, { "defaultMessage": "!!!Service Proxies", "end": { "column": 3, - "line": 35 + "line": 56 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.serviceProxies", "start": { "column": 18, - "line": 32 + "line": 53 } }, { "defaultMessage": "!!!Team Management", "end": { "column": 3, - "line": 39 + "line": 60 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.teamManagement", "start": { "column": 18, - "line": 36 + "line": 57 } }, { "defaultMessage": "!!!No Waiting Screens", "end": { "column": 3, - "line": 43 + "line": 64 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.appDelays", "start": { "column": 13, - "line": 40 + "line": 61 } }, { "defaultMessage": "!!!Forever ad-free", "end": { "column": 3, - "line": 47 + "line": 68 }, "file": "src/components/ui/FeatureList.js", "id": "pricing.features.adFree", "start": { "column": 10, - "line": 44 + "line": 65 } } ], @@ -3831,6 +3896,273 @@ ], "path": "src/features/delayApp/Component.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!per month", + "end": { + "column": 3, + "line": 22 + }, + "file": "src/features/planSelection/components/PlanItem.js", + "id": "subscription.interval.perMonth", + "start": { + "column": 12, + "line": 19 + } + }, + { + "defaultMessage": "!!!per month & user", + "end": { + "column": 3, + "line": 26 + }, + "file": "src/features/planSelection/components/PlanItem.js", + "id": "subscription.interval.perMonthPerUser", + "start": { + "column": 19, + "line": 23 + } + } + ], + "path": "src/features/planSelection/components/PlanItem.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Welcome back, {name}", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.fullscreen.welcome", + "start": { + "column": 11, + "line": 16 + } + }, + { + "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", + "end": { + "column": 3, + "line": 23 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.fullscreen.subheadline", + "start": { + "column": 15, + "line": 20 + } + }, + { + "defaultMessage": "!!!Basic functionality", + "end": { + "column": 3, + "line": 27 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.free.text", + "start": { + "column": 12, + "line": 24 + } + }, + { + "defaultMessage": "!!!More services, no waiting - ideal for personal use.", + "end": { + "column": 3, + "line": 31 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.personal.text", + "start": { + "column": 16, + "line": 28 + } + }, + { + "defaultMessage": "!!!Unlimited services and professional features for you - and your team.", + "end": { + "column": 3, + "line": 35 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.pro.text", + "start": { + "column": 20, + "line": 32 + } + }, + { + "defaultMessage": "!!!Stay on Free", + "end": { + "column": 3, + "line": 39 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.cta.stayOnFree", + "start": { + "column": 17, + "line": 36 + } + }, + { + "defaultMessage": "!!!Downgrade to Free", + "end": { + "column": 3, + "line": 43 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.cta.ctaDowngradeFree", + "start": { + "column": 20, + "line": 40 + } + }, + { + "defaultMessage": "!!!Start my free 14-days Trial", + "end": { + "column": 3, + "line": 47 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.cta.trial", + "start": { + "column": 15, + "line": 44 + } + }, + { + "defaultMessage": "!!!Choose Personal", + "end": { + "column": 3, + "line": 51 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.cta.upgradePersonal", + "start": { + "column": 23, + "line": 48 + } + }, + { + "defaultMessage": "!!!Choose Professional", + "end": { + "column": 3, + "line": 55 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.cta.upgradePro", + "start": { + "column": 18, + "line": 52 + } + }, + { + "defaultMessage": "!!!Complete comparison of all plans", + "end": { + "column": 3, + "line": 59 + }, + "file": "src/features/planSelection/components/PlanSelection.js", + "id": "feature.planSelection.fullFeatureList", + "start": { + "column": 19, + "line": 56 + } + } + ], + "path": "src/features/planSelection/components/PlanSelection.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!per {interval}", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/features/planSelection/components/PlanTeaser.js", + "id": "subscription.interval.per", + "start": { + "column": 7, + "line": 16 + } + }, + { + "defaultMessage": "!!!Upgrade Account", + "end": { + "column": 3, + "line": 23 + }, + "file": "src/features/planSelection/components/PlanTeaser.js", + "id": "subscription.planItem.upgradeAccount", + "start": { + "column": 7, + "line": 20 + } + } + ], + "path": "src/features/planSelection/components/PlanTeaser.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Downgrade your Franz Plan", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "id": "feature.planSelection.fullscreen.dialog.title", + "start": { + "column": 15, + "line": 16 + } + }, + { + "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", + "end": { + "column": 3, + "line": 23 + }, + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "id": "feature.planSelection.fullscreen.dialog.message", + "start": { + "column": 17, + "line": 20 + } + }, + { + "defaultMessage": "!!!Downgrade to Free", + "end": { + "column": 3, + "line": 27 + }, + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "id": "feature.planSelection.fullscreen.dialog.cta.downgrade", + "start": { + "column": 22, + "line": 24 + } + }, + { + "defaultMessage": "!!!Choose Personal", + "end": { + "column": 3, + "line": 31 + }, + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "id": "feature.planSelection.fullscreen.dialog.cta.upgrade", + "start": { + "column": 20, + "line": 28 + } + } + ], + "path": "src/features/planSelection/containers/PlanSelectionScreen.json" + }, { "descriptors": [ { @@ -4487,7 +4819,7 @@ { "descriptors": [ { - "defaultMessage": "!!!Franz Professional", + "defaultMessage": "!!!Professional", "end": { "column": 3, "line": 8 @@ -4500,7 +4832,7 @@ } }, { - "defaultMessage": "!!!Franz Personal", + "defaultMessage": "!!!Personal", "end": { "column": 3, "line": 12 @@ -4513,7 +4845,7 @@ } }, { - "defaultMessage": "!!!Franz Free", + "defaultMessage": "!!!Free", "end": { "column": 3, "line": 16 @@ -4526,7 +4858,7 @@ } }, { - "defaultMessage": "!!!Franz Premium", + "defaultMessage": "!!!Premium", "end": { "column": 3, "line": 20 diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index aea74768d..4e1b01419 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -9,6 +9,21 @@ "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.planSelection.cta.ctaDowngradeFree": "Downgrade to Free", + "feature.planSelection.cta.stayOnFree": "Stay on Free", + "feature.planSelection.cta.trial": "Start my free 14-days Trial", + "feature.planSelection.cta.upgradePersonal": "Choose Personal", + "feature.planSelection.cta.upgradePro": "Choose Professional", + "feature.planSelection.free.text": "Basic functionality", + "feature.planSelection.fullFeatureList": "Complete comparison of all plans", + "feature.planSelection.fullscreen.dialog.cta.downgrade": "Downgrade to Free", + "feature.planSelection.fullscreen.dialog.cta.upgrade": "Choose Personal", + "feature.planSelection.fullscreen.dialog.message": "You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", + "feature.planSelection.fullscreen.dialog.title": "Downgrade your Franz Plan", + "feature.planSelection.fullscreen.subheadline": "It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", + "feature.planSelection.fullscreen.welcome": "Welcome back, {name}", + "feature.planSelection.personal.text": "More services, no waiting - ideal for personal use.", + "feature.planSelection.pro.text": "Unlimited services and professional features for you - and your team.", "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", @@ -123,30 +138,35 @@ "password.submit.label": "Submit", "password.successInfo": "Please check your email", "premiumFeature.button.upgradeAccount": "Upgrade account", + "pricing.features.accountSync": "Account Synchronisation", "pricing.features.adFree": "Forever ad-free", "pricing.features.appDelays": "No Waiting Screens", "pricing.features.customWebsites": "Add Custom Websites", + "pricing.features.desktopNotifications": "Desktop Notifications", "pricing.features.onPremise": "On-premise & other Hosted Services", + "pricing.features.recipes": "Choose from more than 70 Services", "pricing.features.serviceProxies": "Service Proxies", "pricing.features.spellchecker": "Spellchecker support", "pricing.features.teamManagement": "Team Management", "pricing.features.thirdPartyServices": "Install 3rd party services", "pricing.features.unlimitedServices": "Add unlimited services", + "pricing.features.upToSixServices": "Add up to 6 services", + "pricing.features.upToThreeServices": "Add up to 3 services", "pricing.features.workspaces": "Workspaces", - "pricing.plan.free": "Franz Free", - "pricing.plan.legacy": "Franz Premium", - "pricing.plan.personal": "Franz Personal", - "pricing.plan.personal-monthly": "Franz Personal Monthly", - "pricing.plan.personal-yearly": "Franz Personal Yearly", - "pricing.plan.pro": "Franz Professional", - "pricing.plan.pro-monthly": "Franz Professional Monthly", - "pricing.plan.pro-yearly": "Franz Professional Yearly", - "pricing.trial.cta.accept": "Yes, upgrade my account to Franz Professional", + "pricing.plan.free": "Free", + "pricing.plan.legacy": "Premium", + "pricing.plan.personal": "Personal", + "pricing.plan.personal-monthly": "Personal Monthly", + "pricing.plan.personal-yearly": "Personal Yearly", + "pricing.plan.pro": "Professional", + "pricing.plan.pro-monthly": "Professional Monthly", + "pricing.plan.pro-yearly": "Professional Yearly", + "pricing.trial.cta.accept": "Start my 14-day Franz Professional Trial ", "pricing.trial.cta.skip": "Continue to Franz", "pricing.trial.error": "Sorry, we could not activate your trial!", "pricing.trial.features.headline": "Franz Professional includes:", "pricing.trial.headline": "Franz Professional", - "pricing.trial.subheadline": "Your personal welcome offer:", + "pricing.trial.subheadline": "Here's a special welcome for you:", "pricing.trial.terms.automaticTrialEnd": "Your free trial ends automatically after 14 days", "pricing.trial.terms.headline": "No strings attached", "pricing.trial.terms.noCreditCard": "No credit card required", @@ -353,6 +373,10 @@ "subscription.cta.allOptions": "See all options", "subscription.cta.choosePlan": "Choose your plan", "subscription.includedProFeatures": "The Franz Professional Plan includes:", + "subscription.interval.per": "per {interval}", + "subscription.interval.perMonth": "per month", + "subscription.interval.perMonthPerUser": "per month & user", + "subscription.planItem.upgradeAccount": "Upgrade Account", "subscription.teaser.includedFeatures": "Paid Franz Plans include:", "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!", "subscriptionPopup.buttonCancel": "Cancel", @@ -389,4 +413,4 @@ "workspaceDrawer.workspaceFeatureInfo": "

Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", "workspaces.switchingIndicator.switchingTo": "Switching to" -} \ 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 f15617ca5..2d09c9ebb 100644 --- a/src/i18n/messages/src/components/auth/Pricing.json +++ b/src/i18n/messages/src/components/auth/Pricing.json @@ -14,7 +14,7 @@ }, { "id": "pricing.trial.subheadline", - "defaultMessage": "!!!Your personal welcome offer:", + "defaultMessage": "!!!Here's a special welcome for you:", "file": "src/components/auth/Pricing.js", "start": { "line": 19, @@ -79,7 +79,7 @@ }, { "id": "pricing.trial.cta.accept", - "defaultMessage": "!!!Yes, upgrade my account to Franz Professional", + "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "file": "src/components/auth/Pricing.js", "start": { "line": 39, diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json index 44cf4fab9..22f11cedd 100644 --- a/src/i18n/messages/src/components/layout/AppLayout.json +++ b/src/i18n/messages/src/components/layout/AppLayout.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!Your services have been updated.", "file": "src/components/layout/AppLayout.js", "start": { - "line": 28, + "line": 29, "column": 19 }, "end": { - "line": 31, + "line": 32, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Reload services", "file": "src/components/layout/AppLayout.js", "start": { - "line": 32, + "line": 33, "column": 24 }, "end": { - "line": 35, + "line": 36, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Could not load services and user information", "file": "src/components/layout/AppLayout.js", "start": { - "line": 36, + "line": 37, "column": 26 }, "end": { - "line": 39, + "line": 40, "column": 3 } } diff --git a/src/i18n/messages/src/components/ui/FeatureList.json b/src/i18n/messages/src/components/ui/FeatureList.json index 497e299a4..3201115b3 100644 --- a/src/i18n/messages/src/components/ui/FeatureList.json +++ b/src/i18n/messages/src/components/ui/FeatureList.json @@ -1,14 +1,79 @@ [ + { + "id": "pricing.features.recipes", + "defaultMessage": "!!!Choose from more than 70 Services", + "file": "src/components/ui/FeatureList.js", + "start": { + "line": 9, + "column": 20 + }, + "end": { + "line": 12, + "column": 3 + } + }, + { + "id": "pricing.features.accountSync", + "defaultMessage": "!!!Account Synchronisation", + "file": "src/components/ui/FeatureList.js", + "start": { + "line": 13, + "column": 15 + }, + "end": { + "line": 16, + "column": 3 + } + }, + { + "id": "pricing.features.desktopNotifications", + "defaultMessage": "!!!Desktop Notifications", + "file": "src/components/ui/FeatureList.js", + "start": { + "line": 17, + "column": 24 + }, + "end": { + "line": 20, + "column": 3 + } + }, { "id": "pricing.features.unlimitedServices", "defaultMessage": "!!!Add unlimited services", "file": "src/components/ui/FeatureList.js", "start": { - "line": 8, + "line": 21, "column": 21 }, "end": { - "line": 11, + "line": 24, + "column": 3 + } + }, + { + "id": "pricing.features.upToThreeServices", + "defaultMessage": "!!!Add up to 3 services", + "file": "src/components/ui/FeatureList.js", + "start": { + "line": 25, + "column": 21 + }, + "end": { + "line": 28, + "column": 3 + } + }, + { + "id": "pricing.features.upToSixServices", + "defaultMessage": "!!!Add up to 6 services", + "file": "src/components/ui/FeatureList.js", + "start": { + "line": 29, + "column": 19 + }, + "end": { + "line": 32, "column": 3 } }, @@ -17,11 +82,11 @@ "defaultMessage": "!!!Spellchecker support", "file": "src/components/ui/FeatureList.js", "start": { - "line": 12, + "line": 33, "column": 16 }, "end": { - "line": 15, + "line": 36, "column": 3 } }, @@ -30,11 +95,11 @@ "defaultMessage": "!!!Workspaces", "file": "src/components/ui/FeatureList.js", "start": { - "line": 16, + "line": 37, "column": 14 }, "end": { - "line": 19, + "line": 40, "column": 3 } }, @@ -43,11 +108,11 @@ "defaultMessage": "!!!Add Custom Websites", "file": "src/components/ui/FeatureList.js", "start": { - "line": 20, + "line": 41, "column": 18 }, "end": { - "line": 23, + "line": 44, "column": 3 } }, @@ -56,11 +121,11 @@ "defaultMessage": "!!!On-premise & other Hosted Services", "file": "src/components/ui/FeatureList.js", "start": { - "line": 24, + "line": 45, "column": 13 }, "end": { - "line": 27, + "line": 48, "column": 3 } }, @@ -69,11 +134,11 @@ "defaultMessage": "!!!Install 3rd party services", "file": "src/components/ui/FeatureList.js", "start": { - "line": 28, + "line": 49, "column": 22 }, "end": { - "line": 31, + "line": 52, "column": 3 } }, @@ -82,11 +147,11 @@ "defaultMessage": "!!!Service Proxies", "file": "src/components/ui/FeatureList.js", "start": { - "line": 32, + "line": 53, "column": 18 }, "end": { - "line": 35, + "line": 56, "column": 3 } }, @@ -95,11 +160,11 @@ "defaultMessage": "!!!Team Management", "file": "src/components/ui/FeatureList.js", "start": { - "line": 36, + "line": 57, "column": 18 }, "end": { - "line": 39, + "line": 60, "column": 3 } }, @@ -108,11 +173,11 @@ "defaultMessage": "!!!No Waiting Screens", "file": "src/components/ui/FeatureList.js", "start": { - "line": 40, + "line": 61, "column": 13 }, "end": { - "line": 43, + "line": 64, "column": 3 } }, @@ -121,11 +186,11 @@ "defaultMessage": "!!!Forever ad-free", "file": "src/components/ui/FeatureList.js", "start": { - "line": 44, + "line": 65, "column": 10 }, "end": { - "line": 47, + "line": 68, "column": 3 } } diff --git a/src/i18n/messages/src/features/planSelection/components/PlanItem.json b/src/i18n/messages/src/features/planSelection/components/PlanItem.json new file mode 100644 index 000000000..839686390 --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanItem.json @@ -0,0 +1,28 @@ +[ + { + "id": "subscription.interval.perMonth", + "defaultMessage": "!!!per month", + "file": "src/features/planSelection/components/PlanItem.js", + "start": { + "line": 19, + "column": 12 + }, + "end": { + "line": 22, + "column": 3 + } + }, + { + "id": "subscription.interval.perMonthPerUser", + "defaultMessage": "!!!per month & user", + "file": "src/features/planSelection/components/PlanItem.js", + "start": { + "line": 23, + "column": 19 + }, + "end": { + "line": 26, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/i18n/messages/src/features/planSelection/components/PlanSelection.json b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json new file mode 100644 index 000000000..3dc4f74f4 --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanSelection.json @@ -0,0 +1,145 @@ +[ + { + "id": "feature.planSelection.fullscreen.welcome", + "defaultMessage": "!!!Welcome back, {name}", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 16, + "column": 11 + }, + "end": { + "line": 19, + "column": 3 + } + }, + { + "id": "feature.planSelection.fullscreen.subheadline", + "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 20, + "column": 15 + }, + "end": { + "line": 23, + "column": 3 + } + }, + { + "id": "feature.planSelection.free.text", + "defaultMessage": "!!!Basic functionality", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 24, + "column": 12 + }, + "end": { + "line": 27, + "column": 3 + } + }, + { + "id": "feature.planSelection.personal.text", + "defaultMessage": "!!!More services, no waiting - ideal for personal use.", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 28, + "column": 16 + }, + "end": { + "line": 31, + "column": 3 + } + }, + { + "id": "feature.planSelection.pro.text", + "defaultMessage": "!!!Unlimited services and professional features for you - and your team.", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 32, + "column": 20 + }, + "end": { + "line": 35, + "column": 3 + } + }, + { + "id": "feature.planSelection.cta.stayOnFree", + "defaultMessage": "!!!Stay on Free", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 36, + "column": 17 + }, + "end": { + "line": 39, + "column": 3 + } + }, + { + "id": "feature.planSelection.cta.ctaDowngradeFree", + "defaultMessage": "!!!Downgrade to Free", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 40, + "column": 20 + }, + "end": { + "line": 43, + "column": 3 + } + }, + { + "id": "feature.planSelection.cta.trial", + "defaultMessage": "!!!Start my free 14-days Trial", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 44, + "column": 15 + }, + "end": { + "line": 47, + "column": 3 + } + }, + { + "id": "feature.planSelection.cta.upgradePersonal", + "defaultMessage": "!!!Choose Personal", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 48, + "column": 23 + }, + "end": { + "line": 51, + "column": 3 + } + }, + { + "id": "feature.planSelection.cta.upgradePro", + "defaultMessage": "!!!Choose Professional", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 52, + "column": 18 + }, + "end": { + "line": 55, + "column": 3 + } + }, + { + "id": "feature.planSelection.fullFeatureList", + "defaultMessage": "!!!Complete comparison of all plans", + "file": "src/features/planSelection/components/PlanSelection.js", + "start": { + "line": 56, + "column": 19 + }, + "end": { + "line": 59, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json new file mode 100644 index 000000000..015304a2e --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/components/PlanTeaser.json @@ -0,0 +1,28 @@ +[ + { + "id": "subscription.interval.per", + "defaultMessage": "!!!per {interval}", + "file": "src/features/planSelection/components/PlanTeaser.js", + "start": { + "line": 16, + "column": 7 + }, + "end": { + "line": 19, + "column": 3 + } + }, + { + "id": "subscription.planItem.upgradeAccount", + "defaultMessage": "!!!Upgrade Account", + "file": "src/features/planSelection/components/PlanTeaser.js", + "start": { + "line": 20, + "column": 7 + }, + "end": { + "line": 23, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json new file mode 100644 index 000000000..04b2144b4 --- /dev/null +++ b/src/i18n/messages/src/features/planSelection/containers/PlanSelectionScreen.json @@ -0,0 +1,54 @@ +[ + { + "id": "feature.planSelection.fullscreen.dialog.title", + "defaultMessage": "!!!Downgrade your Franz Plan", + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "start": { + "line": 16, + "column": 15 + }, + "end": { + "line": 19, + "column": 3 + } + }, + { + "id": "feature.planSelection.fullscreen.dialog.message", + "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "start": { + "line": 20, + "column": 17 + }, + "end": { + "line": 23, + "column": 3 + } + }, + { + "id": "feature.planSelection.fullscreen.dialog.cta.downgrade", + "defaultMessage": "!!!Downgrade to Free", + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "start": { + "line": 24, + "column": 22 + }, + "end": { + "line": 27, + "column": 3 + } + }, + { + "id": "feature.planSelection.fullscreen.dialog.cta.upgrade", + "defaultMessage": "!!!Choose Personal", + "file": "src/features/planSelection/containers/PlanSelectionScreen.js", + "start": { + "line": 28, + "column": 20 + }, + "end": { + "line": 31, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/i18n/messages/src/helpers/plan-helpers.json b/src/i18n/messages/src/helpers/plan-helpers.json index df8ee19e3..3f3e7e85d 100644 --- a/src/i18n/messages/src/helpers/plan-helpers.json +++ b/src/i18n/messages/src/helpers/plan-helpers.json @@ -1,7 +1,7 @@ [ { "id": "pricing.plan.pro", - "defaultMessage": "!!!Franz Professional", + "defaultMessage": "!!!Professional", "file": "src/helpers/plan-helpers.js", "start": { "line": 5, @@ -14,7 +14,7 @@ }, { "id": "pricing.plan.personal", - "defaultMessage": "!!!Franz Personal", + "defaultMessage": "!!!Personal", "file": "src/helpers/plan-helpers.js", "start": { "line": 9, @@ -27,7 +27,7 @@ }, { "id": "pricing.plan.free", - "defaultMessage": "!!!Franz Free", + "defaultMessage": "!!!Free", "file": "src/helpers/plan-helpers.js", "start": { "line": 13, @@ -40,7 +40,7 @@ }, { "id": "pricing.plan.legacy", - "defaultMessage": "!!!Franz Premium", + "defaultMessage": "!!!Premium", "file": "src/helpers/plan-helpers.js", "start": { "line": 17, diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index cf28b6bec..bffcb01bc 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -19,6 +19,7 @@ import settingsWS from '../features/settingsWS'; import serviceLimit from '../features/serviceLimit'; import communityRecipes from '../features/communityRecipes'; import todos from '../features/todos'; +import planSelection from '../features/planSelection'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -81,5 +82,6 @@ export default class FeaturesStore extends Store { serviceLimit(this.stores, this.actions); communityRecipes(this.stores, this.actions); todos(this.stores, this.actions); + planSelection(this.stores, this.actions); } } diff --git a/src/stores/index.js b/src/stores/index.js index 10dd56665..4eeef7982 100644 --- a/src/stores/index.js +++ b/src/stores/index.js @@ -15,6 +15,7 @@ import { announcementsStore } from '../features/announcements'; import { serviceLimitStore } from '../features/serviceLimit'; import { communityRecipesStore } from '../features/communityRecipes'; import { todosStore } from '../features/todos'; +import { planSelectionStore } from '../features/planSelection'; export default (api, actions, router) => { const stores = {}; @@ -37,6 +38,7 @@ export default (api, actions, router) => { serviceLimit: serviceLimitStore, communityRecipes: communityRecipesStore, todos: todosStore, + planSelection: planSelectionStore, }); // Initialize all stores Object.keys(stores).forEach((name) => { -- cgit v1.2.3-70-g09d2 From 8c194bb9c5423fb9b44d11609dae72ac8497eb85 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 16 Oct 2019 16:54:59 +0200 Subject: add space --- src/components/settings/account/AccountDashboard.js | 1 + src/i18n/locales/en-US.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src/components/settings') diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 9a1b31d0f..776a8fd08 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js @@ -218,6 +218,7 @@ class AccountDashboard extends Component {

Franz + {' '} {isPremiumOverrideUser ? 'Premium' : planName} {user.team.isTrial && ( <> diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 16f4bff46..ce35c29a8 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -422,4 +422,4 @@ "workspaceDrawer.workspaceFeatureInfo": "

Franz Workspaces let you focus on what’s important right now. Set up different sets of services and easily switch between them at any time.

You decide which services you need when and where, so we can help you stay on top of your game - or easily switch off from work whenever you want.

", "workspaceDrawer.workspacesSettingsTooltip": "Edit workspaces settings", "workspaces.switchingIndicator.switchingTo": "Switching to" -} +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 86b692b8e6c9710f216b17a04d96a815a8715387 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 16 Oct 2019 22:24:40 +0200 Subject: polishing --- src/components/settings/account/AccountDashboard.js | 8 +++++--- src/containers/settings/AccountScreen.js | 7 +++++-- src/features/trialStatusBar/containers/TrialStatusBarScreen.js | 2 -- 3 files changed, 10 insertions(+), 7 deletions(-) (limited to 'src/components/settings') diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 776a8fd08..b4ff072ab 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js @@ -242,14 +242,16 @@ class AccountDashboard extends Component {

)} -
- {!isProUser && ( + {!isProUser && ( +
+ )} +