From 5323a190a45644d87bedf89726d74ee92dceeac0 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 7 Oct 2019 20:17:48 +0200 Subject: Also show Franz loading spinner when workspaces are loading --- src/containers/layout/AppLayoutContainer.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/containers') diff --git a/src/containers/layout/AppLayoutContainer.js b/src/containers/layout/AppLayoutContainer.js index a14a98554..5563c48bc 100644 --- a/src/containers/layout/AppLayoutContainer.js +++ b/src/containers/layout/AppLayoutContainer.js @@ -24,6 +24,7 @@ import { state as delayAppState } from '../../features/delayApp'; import { workspaceActions } from '../../features/workspaces/actions'; import WorkspaceDrawer from '../../features/workspaces/components/WorkspaceDrawer'; import { workspaceStore } from '../../features/workspaces'; +import WorkspacesStore from '../../features/workspaces/store'; export default @inject('stores', 'actions') @observer class AppLayoutContainer extends Component { static defaultProps = { @@ -41,6 +42,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e globalError, requests, user, + workspaces, } = this.props.stores; const { @@ -79,7 +81,7 @@ export default @inject('stores', 'actions') @observer class AppLayoutContainer e const isLoadingServices = services.allServicesRequest.isExecuting && services.allServicesRequest.isExecutingFirstTime; - if (isLoadingFeatures || isLoadingServices) { + if (isLoadingFeatures || isLoadingServices || workspaces.isLoadingWorkspaces) { return ( @@ -174,6 +176,7 @@ AppLayoutContainer.wrappedComponent.propTypes = { user: PropTypes.instanceOf(UserStore).isRequired, requests: PropTypes.instanceOf(RequestStore).isRequired, globalError: PropTypes.instanceOf(GlobalErrorStore).isRequired, + workspaces: PropTypes.instanceOf(WorkspacesStore).isRequired, }).isRequired, actions: PropTypes.shape({ service: PropTypes.shape({ -- cgit v1.2.3-70-g09d2 From dea59f20debdfde7279551f5cd2fbf3c390171e0 Mon Sep 17 00:00:00 2001 From: Dominik Guzei Date: Mon, 7 Oct 2019 20:18:38 +0200 Subject: Fix non-related issues --- src/components/ui/PremiumFeatureContainer/index.js | 4 ++-- src/containers/settings/EditSettingsScreen.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/containers') diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js index 8d2746e22..f1e526560 100644 --- a/src/components/ui/PremiumFeatureContainer/index.js +++ b/src/components/ui/PremiumFeatureContainer/index.js @@ -10,7 +10,7 @@ import UserStore from '../../../stores/UserStore'; import styles from './styles'; import { gaEvent } from '../../../lib/analytics'; -import { FeatureStore } from '../../../features/utils/FeatureStore'; +import FeaturesStore from '../../../stores/FeaturesStore'; const messages = defineMessages({ action: { @@ -96,7 +96,7 @@ PremiumFeatureContainer.wrappedComponent.propTypes = { children: oneOrManyChildElements.isRequired, stores: PropTypes.shape({ user: PropTypes.instanceOf(UserStore).isRequired, - features: PropTypes.instanceOf(FeatureStore).isRequired, + features: PropTypes.instanceOf(FeaturesStore).isRequired, }).isRequired, actions: PropTypes.shape({ ui: PropTypes.shape({ diff --git a/src/containers/settings/EditSettingsScreen.js b/src/containers/settings/EditSettingsScreen.js index 9aba212be..698b5a3d9 100644 --- a/src/containers/settings/EditSettingsScreen.js +++ b/src/containers/settings/EditSettingsScreen.js @@ -313,7 +313,7 @@ EditSettingsScreen.wrappedComponent.propTypes = { toggleTodosFeatureVisibility: PropTypes.func.isRequired, }).isRequired, workspaces: PropTypes.shape({ - toggleAllWorkspacesLoadedSetting: PropTypes.func.isRequired, + toggleKeepAllWorkspacesLoadedSetting: PropTypes.func.isRequired, }).isRequired, }).isRequired, }; -- cgit v1.2.3-70-g09d2 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 --- .../settings/account/AccountDashboard.js | 6 +++- src/components/subscription/SubscriptionPopup.js | 2 ++ src/containers/settings/AccountScreen.js | 5 +++- .../subscription/SubscriptionFormScreen.js | 32 ++++++++++++++++++++-- src/styles/subscription-popup.scss | 5 +++- 5 files changed, 45 insertions(+), 5 deletions(-) (limited to 'src/containers') 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 && (
- +
)} diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js index 12ef8a6e9..12a51ad7b 100644 --- a/src/components/subscription/SubscriptionPopup.js +++ b/src/components/subscription/SubscriptionPopup.js @@ -59,8 +59,10 @@ export default @observer class SubscriptionPopup extends Component { className="subscription-popup__webview" autosize + allowpopups src={encodeURI(url)} onDidNavigate={completeCheck} + onDidNavigateInPage={completeCheck} />
diff --git a/src/containers/settings/AccountScreen.js b/src/containers/settings/AccountScreen.js index 9c74cf2ab..b0354c86b 100644 --- a/src/containers/settings/AccountScreen.js +++ b/src/containers/settings/AccountScreen.js @@ -5,6 +5,7 @@ import { inject, observer } from 'mobx-react'; import PaymentStore from '../../stores/PaymentStore'; import UserStore from '../../stores/UserStore'; import AppStore from '../../stores/AppStore'; +import FeaturesStore from '../../stores/FeaturesStore'; import AccountDashboard from '../../components/settings/account/AccountDashboard'; import ErrorBoundary from '../../components/util/ErrorBoundary'; @@ -12,8 +13,9 @@ import { WEBSITE } from '../../environment'; export default @inject('stores', 'actions') @observer class AccountScreen extends Component { onCloseWindow() { - const { user } = this.props.stores; + const { user, features } = this.props.stores; user.getUserInfoRequest.invalidate({ immediately: true }); + features.featuresRequest.invalidate({ immediately: true }); } reloadData() { @@ -65,6 +67,7 @@ export default @inject('stores', 'actions') @observer class AccountScreen extend AccountScreen.wrappedComponent.propTypes = { stores: PropTypes.shape({ user: PropTypes.instanceOf(UserStore).isRequired, + features: PropTypes.instanceOf(FeaturesStore).isRequired, payment: PropTypes.instanceOf(PaymentStore).isRequired, app: PropTypes.instanceOf(AppStore).isRequired, }).isRequired, diff --git a/src/containers/subscription/SubscriptionFormScreen.js b/src/containers/subscription/SubscriptionFormScreen.js index 726b10628..38e46a7ba 100644 --- a/src/containers/subscription/SubscriptionFormScreen.js +++ b/src/containers/subscription/SubscriptionFormScreen.js @@ -1,4 +1,5 @@ import React, { Component } from 'react'; +import { remote } from 'electron'; import PropTypes from 'prop-types'; import { inject, observer } from 'mobx-react'; @@ -7,11 +8,21 @@ import PaymentStore from '../../stores/PaymentStore'; import SubscriptionForm from '../../components/subscription/SubscriptionForm'; import TrialForm from '../../components/subscription/TrialForm'; +const { BrowserWindow } = remote; + export default @inject('stores', 'actions') @observer class SubscriptionFormScreen extends Component { + static propTypes = { + onCloseWindow: PropTypes.func, + } + + static defaultProps = { + onCloseWindow: () => null, + } + async openBrowser() { const { - actions, stores, + onCloseWindow, } = this.props; const { @@ -22,7 +33,24 @@ export default @inject('stores', 'actions') @observer class SubscriptionFormScre let hostedPageURL = features.features.planSelectionURL; hostedPageURL = user.getAuthURL(hostedPageURL); - actions.app.openExternalUrl({ url: hostedPageURL }); + const paymentWindow = new BrowserWindow({ + parent: remote.getCurrentWindow(), + modal: true, + title: 'πŸ”’ Franz Supporter License', + width: 800, + height: window.innerHeight - 100, + maxWidth: 800, + minWidth: 600, + webPreferences: { + nodeIntegration: true, + webviewTag: true, + }, + }); + paymentWindow.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPageURL)}`); + + paymentWindow.on('closed', () => { + onCloseWindow(); + }); } render() { diff --git a/src/styles/subscription-popup.scss b/src/styles/subscription-popup.scss index fb4795d6c..14e05e65d 100644 --- a/src/styles/subscription-popup.scss +++ b/src/styles/subscription-popup.scss @@ -2,7 +2,10 @@ height: 100%; &__content { height: calc(100% - 60px); } - &__webview { height: 100%; } + &__webview { + height: 100%; + background: #FFF; + } &__toolbar { background: $theme-gray-lightest; -- 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/containers') 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 ba578b8a6df8d31136fb170e78b70a71dad85e31 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 16 Oct 2019 10:22:00 +0200 Subject: polishing --- src/components/auth/Pricing.js | 15 +++ src/containers/auth/PricingScreen.js | 11 +++ src/features/planSelection/components/PlanItem.js | 50 +++++++--- .../planSelection/components/PlanSelection.js | 27 +++--- .../containers/PlanSelectionScreen.js | 14 --- src/features/planSelection/store.js | 3 + src/i18n/locales/defaultMessages.json | 102 +++++++++++++-------- src/i18n/locales/en-US.json | 4 +- src/i18n/messages/src/components/auth/Pricing.json | 29 ++++-- .../planSelection/components/PlanItem.json | 19 +++- 10 files changed, 184 insertions(+), 90 deletions(-) (limited to 'src/containers') diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 40ce49814..67af04470 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js @@ -32,6 +32,10 @@ const messages = defineMessages({ id: 'pricing.trial.terms.automaticTrialEnd', defaultMessage: '!!!Your free trial ends automatically after 14 days', }, + trialWorth: { + id: 'pricing.trial.terms.trialWorth', + defaultMessage: '!!!Free trial (normally {currency}{price} per month)', + }, activationError: { id: 'pricing.trial.error', defaultMessage: '!!!Sorry, we could not activate your trial!', @@ -104,6 +108,8 @@ export default @observer @injectSheet(styles) class Signup extends Component { trialActivationError: PropTypes.bool.isRequired, canSkipTrial: PropTypes.bool.isRequired, classes: PropTypes.object.isRequired, + currency: PropTypes.string.isRequired, + price: PropTypes.number.isRequired, }; static contextTypes = { @@ -118,6 +124,8 @@ export default @observer @injectSheet(styles) class Signup extends Component { trialActivationError, canSkipTrial, classes, + currency, + price, } = this.props; const { intl } = this.context; @@ -156,6 +164,13 @@ export default @observer @injectSheet(styles) class Signup extends Component { {intl.formatMessage(messages.noStringsAttachedHeadline)}
    +
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index 8e0ded16a..ff378bd8b 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js @@ -40,6 +40,15 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend const { getUserInfoRequest, activateTrialRequest } = stores.user; const { featuresRequest, features } = stores.features; + const { pricingConfig } = features; + + let currency = '$'; + let price = '5.99'; + if (pricingConfig) { + ({ currency } = pricingConfig); + ({ price } = pricingConfig.plans.pro.yearly); + } + return ( ); } diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js index a49cd40d3..ea04c8448 100644 --- a/src/features/planSelection/components/PlanItem.js +++ b/src/features/planSelection/components/PlanItem.js @@ -10,10 +10,6 @@ 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: { @@ -24,6 +20,10 @@ const messages = defineMessages({ id: 'subscription.interval.perMonthPerUser', defaultMessage: '!!!per month & user', }, + bestValue: { + id: 'subscription.bestValue', + defaultMessage: '!!!Best value', + }, }); const styles = theme => ({ @@ -41,7 +41,6 @@ const styles = theme => ({ marginBottom: 20, fontSize: 30, color: theme.styleTypes.primary.contrast, - // fontWeight: 'bold', }, }, currency: { @@ -58,9 +57,6 @@ const styles = theme => ({ verticalAlign: 20, }, }, - interval: { - // paddingBottom: 40, - }, text: { marginBottom: 'auto', }, @@ -68,10 +64,6 @@ const styles = theme => ({ background: theme.styleTypes.primary.accent, color: theme.styleTypes.primary.contrast, margin: [40, 'auto', 0, 'auto'], - - // '&:active': { - // opacity: 0.7, - // }, }, divider: { width: 40, @@ -83,10 +75,10 @@ const styles = theme => ({ padding: 20, background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), color: theme.styleTypes.primary.contrast, + position: 'relative', }, content: { padding: 20, - // border: [1, 'solid', 'red'], background: '#EFEFEF', }, simpleCTA: { @@ -97,6 +89,20 @@ const styles = theme => ({ fill: theme.styleTypes.primary.accent, }, }, + bestValue: { + background: theme.styleTypes.success.accent, + color: theme.styleTypes.success.contrast, + right: -66, + top: -40, + height: 'auto', + position: 'absolute', + transform: 'rotateZ(45deg)', + textAlign: 'center', + padding: [5, 50], + transformOrigin: 'left bottom', + fontSize: 12, + boxShadow: '0 2px 6px rgba(0,0,0,0.15)', + }, }); @@ -111,6 +117,8 @@ export default @observer @injectSheet(styles) class PlanItem extends Component { simpleCTA: PropTypes.bool, perUser: PropTypes.bool, classes: PropTypes.object.isRequired, + bestValue: PropTypes.bool, + className: PropTypes.string, children: PropTypes.element, }; @@ -118,6 +126,8 @@ export default @observer @injectSheet(styles) class PlanItem extends Component { simpleCTA: false, perUser: false, children: null, + bestValue: false, + className: '', } static contextTypes = { @@ -135,16 +145,26 @@ export default @observer @injectSheet(styles) class PlanItem extends Component { ctaLabel, simpleCTA, perUser, + bestValue, + className, children, } = this.props; const { intl } = this.context; const priceParts = `${price}`.split('.'); - // const intervalName = i18nIntervalName(PAYMENT_INTERVAL.MONTHLY, intl); return ( -
+
+ {bestValue && ( +
+ {intl.formatMessage(messages.bestValue)} +
+ )}

{name}

{text} diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js index 9cd592083..1a45cf035 100644 --- a/src/features/planSelection/components/PlanSelection.js +++ b/src/features/planSelection/components/PlanSelection.js @@ -137,6 +137,9 @@ const styles = theme => ({ border: '1px solid red', overflow: 'scroll-x', }, + featuredPlan: { + transform: 'scale(1.05)', + }, }); @injectSheet(styles) @observer @@ -197,29 +200,31 @@ class PlanSelection extends Component { /> upgradeAccount(plans.personal.yearly.id)} + className={classes.featuredPlan} + perUser + bestValue > upgradeAccount(plans.personal.yearly.id)} - perUser > diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js index 01b357861..dff9051d8 100644 --- a/src/features/planSelection/containers/PlanSelectionScreen.js +++ b/src/features/planSelection/containers/PlanSelectionScreen.js @@ -43,15 +43,12 @@ class PlanSelectionScreen extends Component { } 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 }); }, }); } @@ -68,17 +65,6 @@ class PlanSelectionScreen extends Component { 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 ( { + this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); + this.stores.features.featuresRequest.invalidate({ immediately: true }); + onCloseWindow(); }); }; diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index 210ea001a..eafac1f87 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -482,55 +482,68 @@ } }, { - "defaultMessage": "!!!Sorry, we could not activate your trial!", + "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", "end": { "column": 3, "line": 38 }, "file": "src/components/auth/Pricing.js", + "id": "pricing.trial.terms.trialWorth", + "start": { + "column": 14, + "line": 35 + } + }, + { + "defaultMessage": "!!!Sorry, we could not activate your trial!", + "end": { + "column": 3, + "line": 42 + }, + "file": "src/components/auth/Pricing.js", "id": "pricing.trial.error", "start": { "column": 19, - "line": 35 + "line": 39 } }, { "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "end": { "column": 3, - "line": 42 + "line": 46 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.cta.accept", "start": { "column": 13, - "line": 39 + "line": 43 } }, { "defaultMessage": "!!!Continue to Franz", "end": { "column": 3, - "line": 46 + "line": 50 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.cta.skip", "start": { "column": 11, - "line": 43 + "line": 47 } }, { "defaultMessage": "!!!Franz Professional includes:", "end": { "column": 3, - "line": 50 + "line": 54 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.features.headline", "start": { "column": 20, - "line": 47 + "line": 51 } } ], @@ -3923,6 +3936,19 @@ "column": 19, "line": 23 } + }, + { + "defaultMessage": "!!!Best value", + "end": { + "column": 3, + "line": 30 + }, + "file": "src/features/planSelection/components/PlanItem.js", + "id": "subscription.bestValue", + "start": { + "column": 13, + "line": 27 + } } ], "path": "src/features/planSelection/components/PlanItem.json" @@ -3933,143 +3959,143 @@ "defaultMessage": "!!!Welcome back, {name}", "end": { "column": 3, - "line": 19 + "line": 20 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.fullscreen.welcome", "start": { "column": 11, - "line": 16 + "line": 17 } }, { "defaultMessage": "!!!It's time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.", "end": { "column": 3, - "line": 23 + "line": 24 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.fullscreen.subheadline", "start": { "column": 15, - "line": 20 + "line": 21 } }, { "defaultMessage": "!!!Basic functionality", "end": { "column": 3, - "line": 27 + "line": 28 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.free.text", "start": { "column": 12, - "line": 24 + "line": 25 } }, { "defaultMessage": "!!!More services, no waiting - ideal for personal use.", "end": { "column": 3, - "line": 31 + "line": 32 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.personal.text", "start": { "column": 16, - "line": 28 + "line": 29 } }, { "defaultMessage": "!!!Unlimited services and professional features for you - and your team.", "end": { "column": 3, - "line": 35 + "line": 36 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.pro.text", "start": { "column": 20, - "line": 32 + "line": 33 } }, { "defaultMessage": "!!!Stay on Free", "end": { "column": 3, - "line": 39 + "line": 40 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.cta.stayOnFree", "start": { "column": 17, - "line": 36 + "line": 37 } }, { "defaultMessage": "!!!Downgrade to Free", "end": { "column": 3, - "line": 43 + "line": 44 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.cta.ctaDowngradeFree", "start": { "column": 20, - "line": 40 + "line": 41 } }, { "defaultMessage": "!!!Start my free 14-days Trial", "end": { "column": 3, - "line": 47 + "line": 48 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.cta.trial", "start": { "column": 15, - "line": 44 + "line": 45 } }, { "defaultMessage": "!!!Choose Personal", "end": { "column": 3, - "line": 51 + "line": 52 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.cta.upgradePersonal", "start": { "column": 23, - "line": 48 + "line": 49 } }, { "defaultMessage": "!!!Choose Professional", "end": { "column": 3, - "line": 55 + "line": 56 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.cta.upgradePro", "start": { "column": 18, - "line": 52 + "line": 53 } }, { "defaultMessage": "!!!Complete comparison of all plans", "end": { "column": 3, - "line": 59 + "line": 60 }, "file": "src/features/planSelection/components/PlanSelection.js", "id": "feature.planSelection.fullFeatureList", "start": { "column": 19, - "line": 56 + "line": 57 } } ], @@ -4112,52 +4138,52 @@ "defaultMessage": "!!!Downgrade your Franz Plan", "end": { "column": 3, - "line": 19 + "line": 20 }, "file": "src/features/planSelection/containers/PlanSelectionScreen.js", "id": "feature.planSelection.fullscreen.dialog.title", "start": { "column": 15, - "line": 16 + "line": 17 } }, { "defaultMessage": "!!!You're about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.", "end": { "column": 3, - "line": 23 + "line": 24 }, "file": "src/features/planSelection/containers/PlanSelectionScreen.js", "id": "feature.planSelection.fullscreen.dialog.message", "start": { "column": 17, - "line": 20 + "line": 21 } }, { "defaultMessage": "!!!Downgrade to Free", "end": { "column": 3, - "line": 27 + "line": 28 }, "file": "src/features/planSelection/containers/PlanSelectionScreen.js", "id": "feature.planSelection.fullscreen.dialog.cta.downgrade", "start": { "column": 22, - "line": 24 + "line": 25 } }, { "defaultMessage": "!!!Choose Personal", "end": { "column": 3, - "line": 31 + "line": 32 }, "file": "src/features/planSelection/containers/PlanSelectionScreen.js", "id": "feature.planSelection.fullscreen.dialog.cta.upgrade", "start": { "column": 20, - "line": 28 + "line": 29 } } ], diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index f2fd7a52a..6977ec096 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -170,6 +170,7 @@ "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", + "pricing.trial.terms.trialWorth": "Free trial (normally {currency}{price} per month)", "service.crashHandler.action": "Reload {name}", "service.crashHandler.autoReload": "Trying to automatically restore {name} in {seconds} seconds", "service.crashHandler.headline": "Oh no!", @@ -369,6 +370,7 @@ "signup.link.login": "Already have an account, sign in?", "signup.password.label": "Password", "signup.submit.label": "Create account", + "subscription.bestValue": "Best value", "subscription.cta.activateTrial": "Yes, start the free Franz Professional trial", "subscription.cta.allOptions": "See all options", "subscription.cta.choosePlan": "Choose your plan", @@ -413,4 +415,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 2d09c9ebb..3f0cf4e86 100644 --- a/src/i18n/messages/src/components/auth/Pricing.json +++ b/src/i18n/messages/src/components/auth/Pricing.json @@ -64,16 +64,29 @@ "column": 3 } }, + { + "id": "pricing.trial.terms.trialWorth", + "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", + "file": "src/components/auth/Pricing.js", + "start": { + "line": 35, + "column": 14 + }, + "end": { + "line": 38, + "column": 3 + } + }, { "id": "pricing.trial.error", "defaultMessage": "!!!Sorry, we could not activate your trial!", "file": "src/components/auth/Pricing.js", "start": { - "line": 35, + "line": 39, "column": 19 }, "end": { - "line": 38, + "line": 42, "column": 3 } }, @@ -82,11 +95,11 @@ "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "file": "src/components/auth/Pricing.js", "start": { - "line": 39, + "line": 43, "column": 13 }, "end": { - "line": 42, + "line": 46, "column": 3 } }, @@ -95,11 +108,11 @@ "defaultMessage": "!!!Continue to Franz", "file": "src/components/auth/Pricing.js", "start": { - "line": 43, + "line": 47, "column": 11 }, "end": { - "line": 46, + "line": 50, "column": 3 } }, @@ -108,11 +121,11 @@ "defaultMessage": "!!!Franz Professional includes:", "file": "src/components/auth/Pricing.js", "start": { - "line": 47, + "line": 51, "column": 20 }, "end": { - "line": 50, + "line": 54, "column": 3 } } diff --git a/src/i18n/messages/src/features/planSelection/components/PlanItem.json b/src/i18n/messages/src/features/planSelection/components/PlanItem.json index 839686390..5a94f32ee 100644 --- a/src/i18n/messages/src/features/planSelection/components/PlanItem.json +++ b/src/i18n/messages/src/features/planSelection/components/PlanItem.json @@ -4,11 +4,11 @@ "defaultMessage": "!!!per month", "file": "src/features/planSelection/components/PlanItem.js", "start": { - "line": 19, + "line": 15, "column": 12 }, "end": { - "line": 22, + "line": 18, "column": 3 } }, @@ -17,9 +17,22 @@ "defaultMessage": "!!!per month & user", "file": "src/features/planSelection/components/PlanItem.js", "start": { - "line": 23, + "line": 19, "column": 19 }, + "end": { + "line": 22, + "column": 3 + } + }, + { + "id": "subscription.bestValue", + "defaultMessage": "!!!Best value", + "file": "src/features/planSelection/components/PlanItem.js", + "start": { + "line": 23, + "column": 13 + }, "end": { "line": 26, "column": 3 -- 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/containers') 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 && ( +
+ )} +
diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index ff378bd8b..55811ed23 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js @@ -20,14 +20,19 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend } = this.props; const { activateTrialRequest } = stores.user; - const { defaultTrialPlan } = stores.features.features; + const { defaultTrialPlan, canSkipTrial } = stores.features.anonymousFeatures; - actions.user.activateTrial({ planId: defaultTrialPlan }); - await activateTrialRequest._promise; - - if (!activateTrialRequest.isError) { + if (!canSkipTrial) { stores.router.push('/'); stores.user.hasCompletedSignup = true; + } else { + actions.user.activateTrial({ planId: defaultTrialPlan }); + await activateTrialRequest._promise; + + if (!activateTrialRequest.isError) { + stores.router.push('/'); + stores.user.hasCompletedSignup = true; + } } } @@ -43,7 +48,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend const { pricingConfig } = features; let currency = '$'; - let price = '5.99'; + let price = 5.99; if (pricingConfig) { ({ currency } = pricingConfig); ({ price } = pricingConfig.plans.pro.yearly); diff --git a/src/containers/auth/SignupScreen.js b/src/containers/auth/SignupScreen.js index efc7ea4c1..f93498be2 100644 --- a/src/containers/auth/SignupScreen.js +++ b/src/containers/auth/SignupScreen.js @@ -4,6 +4,7 @@ import { inject, observer } from 'mobx-react'; import Signup from '../../components/auth/Signup'; import UserStore from '../../stores/UserStore'; +import FeaturesStore from '../../stores/FeaturesStore'; import { globalError as globalErrorPropType } from '../../prop-types'; @@ -12,11 +13,27 @@ export default @inject('stores', 'actions') @observer class SignupScreen extends error: globalErrorPropType.isRequired, }; + onSignup(values) { + const { actions, stores } = this.props; + + const { canSkipTrial, defaultTrialPlan, pricingConfig } = stores.features.anonymousFeatures; + + if (!canSkipTrial) { + Object.assign(values, { + plan: defaultTrialPlan, + currency: pricingConfig.currencyID, + }); + } + + actions.user.signup(values); + } + render() { - const { actions, stores, error } = this.props; + const { stores, error } = this.props; + return ( this.onSignup(values)} isSubmitting={stores.user.signupRequest.isExecuting} loginRoute={stores.user.loginRoute} error={error} @@ -33,5 +50,6 @@ SignupScreen.wrappedComponent.propTypes = { }).isRequired, stores: PropTypes.shape({ user: PropTypes.instanceOf(UserStore).isRequired, + features: PropTypes.instanceOf(FeaturesStore).isRequired, }).isRequired, }; diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index cd876c0ea..e46c69d67 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -417,133 +417,185 @@ { "descriptors": [ { - "defaultMessage": "!!!Franz Professional", + "defaultMessage": "!!!Hi {name}, welcome to Franz", "end": { "column": 3, "line": 18 }, "file": "src/components/auth/Pricing.js", - "id": "pricing.trial.headline", + "id": "pricing.trial.headline.pro", "start": { "column": 12, "line": 15 } }, { - "defaultMessage": "!!!Here's a special welcome for you:", + "defaultMessage": "!!!We have a special treat for you.", "end": { "column": 3, "line": 22 }, "file": "src/components/auth/Pricing.js", + "id": "pricing.trial.intro.specialTreat", + "start": { + "column": 16, + "line": 19 + } + }, + { + "defaultMessage": "!!!Try out the full Franz Professional experience completely free for 14 days.", + "end": { + "column": 3, + "line": 26 + }, + "file": "src/components/auth/Pricing.js", + "id": "pricing.trial.intro.tryPro", + "start": { + "column": 10, + "line": 23 + } + }, + { + "defaultMessage": "!!!Happy messaging,", + "end": { + "column": 3, + "line": 30 + }, + "file": "src/components/auth/Pricing.js", + "id": "pricing.trial.intro.happyMessaging", + "start": { + "column": 18, + "line": 27 + } + }, + { + "defaultMessage": "!!!Here's a special welcome for you:", + "end": { + "column": 3, + "line": 34 + }, + "file": "src/components/auth/Pricing.js", "id": "pricing.trial.subheadline", "start": { "column": 17, - "line": 19 + "line": 31 } }, { "defaultMessage": "!!!No strings attached", "end": { "column": 3, - "line": 26 + "line": 38 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.terms.headline", "start": { "column": 29, - "line": 23 + "line": 35 } }, { "defaultMessage": "!!!No credit card required", "end": { "column": 3, - "line": 30 + "line": 42 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.terms.noCreditCard", "start": { "column": 16, - "line": 27 + "line": 39 } }, { "defaultMessage": "!!!Your free trial ends automatically after 14 days", "end": { "column": 3, - "line": 34 + "line": 46 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.terms.automaticTrialEnd", "start": { "column": 21, - "line": 31 + "line": 43 } }, { "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", "end": { "column": 3, - "line": 38 + "line": 50 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.terms.trialWorth", "start": { "column": 14, - "line": 35 + "line": 47 } }, { "defaultMessage": "!!!Sorry, we could not activate your trial!", "end": { "column": 3, - "line": 42 + "line": 54 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.error", "start": { "column": 19, - "line": 39 + "line": 51 } }, { "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "end": { "column": 3, - "line": 46 + "line": 58 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.cta.accept", "start": { "column": 13, - "line": 43 + "line": 55 + } + }, + { + "defaultMessage": "!!!Start using Franz", + "end": { + "column": 3, + "line": 62 + }, + "file": "src/components/auth/Pricing.js", + "id": "pricing.trial.cta.start", + "start": { + "column": 12, + "line": 59 } }, { "defaultMessage": "!!!Continue to Franz", "end": { "column": 3, - "line": 50 + "line": 66 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.cta.skip", "start": { "column": 11, - "line": 47 + "line": 63 } }, { "defaultMessage": "!!!Franz Professional includes:", "end": { "column": 3, - "line": 54 + "line": 70 }, "file": "src/components/auth/Pricing.js", "id": "pricing.trial.features.headline", "start": { "column": 20, - "line": 51 + "line": 67 } } ], @@ -3956,7 +4008,7 @@ { "descriptors": [ { - "defaultMessage": "!!!Welcome back, {name}", + "defaultMessage": "!!!Are you ready to choose, {name}", "end": { "column": 3, "line": 20 diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 8af42554b..6b0aebb13 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -170,9 +170,13 @@ "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.cta.start": "Start using 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.headline.pro": "Hi {name}, welcome to Franz", + "pricing.trial.intro.happyMessaging": "Happy messaging,", + "pricing.trial.intro.specialTreat": "We have a special treat for you.", + "pricing.trial.intro.tryPro": "Enjoy the full Franz Professional experience completely free for 14 days.", "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", diff --git a/src/i18n/messages/src/components/auth/Pricing.json b/src/i18n/messages/src/components/auth/Pricing.json index 3f0cf4e86..6db39148c 100644 --- a/src/i18n/messages/src/components/auth/Pricing.json +++ b/src/i18n/messages/src/components/auth/Pricing.json @@ -1,7 +1,7 @@ [ { - "id": "pricing.trial.headline", - "defaultMessage": "!!!Franz Professional", + "id": "pricing.trial.headline.pro", + "defaultMessage": "!!!Hi {name}, welcome to Franz", "file": "src/components/auth/Pricing.js", "start": { "line": 15, @@ -13,28 +13,54 @@ } }, { - "id": "pricing.trial.subheadline", - "defaultMessage": "!!!Here's a special welcome for you:", + "id": "pricing.trial.intro.specialTreat", + "defaultMessage": "!!!We have a special treat for you.", "file": "src/components/auth/Pricing.js", "start": { "line": 19, - "column": 17 + "column": 16 }, "end": { "line": 22, "column": 3 } }, + { + "id": "pricing.trial.intro.tryPro", + "defaultMessage": "!!!Enjoy the full Franz Professional experience completely free for 14 days.", + "file": "src/components/auth/Pricing.js", + "start": { + "line": 23, + "column": 10 + }, + "end": { + "line": 26, + "column": 3 + } + }, + { + "id": "pricing.trial.intro.happyMessaging", + "defaultMessage": "!!!Happy messaging,", + "file": "src/components/auth/Pricing.js", + "start": { + "line": 27, + "column": 18 + }, + "end": { + "line": 30, + "column": 3 + } + }, { "id": "pricing.trial.terms.headline", "defaultMessage": "!!!No strings attached", "file": "src/components/auth/Pricing.js", "start": { - "line": 23, + "line": 31, "column": 29 }, "end": { - "line": 26, + "line": 34, "column": 3 } }, @@ -43,11 +69,11 @@ "defaultMessage": "!!!No credit card required", "file": "src/components/auth/Pricing.js", "start": { - "line": 27, + "line": 35, "column": 16 }, "end": { - "line": 30, + "line": 38, "column": 3 } }, @@ -56,11 +82,11 @@ "defaultMessage": "!!!Your free trial ends automatically after 14 days", "file": "src/components/auth/Pricing.js", "start": { - "line": 31, + "line": 39, "column": 21 }, "end": { - "line": 34, + "line": 42, "column": 3 } }, @@ -69,11 +95,11 @@ "defaultMessage": "!!!Free trial (normally {currency}{price} per month)", "file": "src/components/auth/Pricing.js", "start": { - "line": 35, + "line": 43, "column": 14 }, "end": { - "line": 38, + "line": 46, "column": 3 } }, @@ -82,11 +108,11 @@ "defaultMessage": "!!!Sorry, we could not activate your trial!", "file": "src/components/auth/Pricing.js", "start": { - "line": 39, + "line": 47, "column": 19 }, "end": { - "line": 42, + "line": 50, "column": 3 } }, @@ -95,11 +121,24 @@ "defaultMessage": "!!!Start my 14-day Franz Professional Trial", "file": "src/components/auth/Pricing.js", "start": { - "line": 43, + "line": 51, "column": 13 }, "end": { - "line": 46, + "line": 54, + "column": 3 + } + }, + { + "id": "pricing.trial.cta.start", + "defaultMessage": "!!!Start using Franz", + "file": "src/components/auth/Pricing.js", + "start": { + "line": 55, + "column": 12 + }, + "end": { + "line": 58, "column": 3 } }, @@ -108,11 +147,11 @@ "defaultMessage": "!!!Continue to Franz", "file": "src/components/auth/Pricing.js", "start": { - "line": 47, + "line": 59, "column": 11 }, "end": { - "line": 50, + "line": 62, "column": 3 } }, @@ -121,11 +160,11 @@ "defaultMessage": "!!!Franz Professional includes:", "file": "src/components/auth/Pricing.js", "start": { - "line": 51, + "line": 63, "column": 20 }, "end": { - "line": 54, + "line": 66, "column": 3 } } diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index 5d379fd3e..adbd401b4 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -67,6 +67,7 @@ export default class FeaturesStore extends Store { if (this.stores.user.isLoggedIn) { this.featuresRequest.invalidate({ immediately: true }); } else { + this.defaultFeaturesRequest.execute(); this.defaultFeaturesRequest.invalidate({ immediately: true }); } } diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index 735e8f886..297ea1121 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -205,7 +205,7 @@ export default class UserStore extends Store { } @action async _signup({ - firstname, lastname, email, password, accountType, company, + firstname, lastname, email, password, accountType, company, plan, currency, }) { const authToken = await this.signupRequest.execute({ firstname, @@ -215,6 +215,8 @@ export default class UserStore extends Store { accountType, company, locale: this.stores.app.locale, + plan, + currency, }); this.hasCompletedSignup = false; -- cgit v1.2.3-70-g09d2 From 71b9e05d45bac8a592b859dbb707fe4ac8ff4ffd Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Fri, 18 Oct 2019 14:30:29 +0200 Subject: Fix name in pricing screen --- src/components/auth/Pricing.js | 4 +++- src/containers/auth/PricingScreen.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) (limited to 'src/containers') diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 86b6a4263..53ae046a0 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js @@ -150,6 +150,7 @@ export default @observer @injectSheet(styles) class Signup extends Component { classes: PropTypes.object.isRequired, currency: PropTypes.string.isRequired, price: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, }; static contextTypes = { @@ -166,6 +167,7 @@ export default @observer @injectSheet(styles) class Signup extends Component { classes, currency, price, + name, } = this.props; const { intl } = this.context; @@ -182,7 +184,7 @@ export default @observer @injectSheet(styles) class Signup extends Component { alt="" /> )} -

{intl.formatMessage(messages.headline, { name: 'Stefan' })}

+

{intl.formatMessage(messages.headline, { name })}

{intl.formatMessage(messages.specialTreat)} diff --git a/src/containers/auth/PricingScreen.js b/src/containers/auth/PricingScreen.js index 55811ed23..21c859c12 100644 --- a/src/containers/auth/PricingScreen.js +++ b/src/containers/auth/PricingScreen.js @@ -42,7 +42,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend stores, } = this.props; - const { getUserInfoRequest, activateTrialRequest } = stores.user; + const { getUserInfoRequest, activateTrialRequest, data } = stores.user; const { featuresRequest, features } = stores.features; const { pricingConfig } = features; @@ -64,6 +64,7 @@ export default @inject('stores', 'actions') @observer class PricingScreen extend error={error} currency={currency} price={price} + name={data.firstname} /> ); } -- cgit v1.2.3-70-g09d2