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 --- src/stores/FeaturesStore.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/stores/FeaturesStore.js') 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); } } -- cgit v1.2.3-70-g09d2 From 0b08e1e7e6a07acd21af71fd27f4c4acfa34dbba Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 16 Oct 2019 15:16:26 +0200 Subject: Add trialStatusBar & polishing --- packages/theme/src/themes/dark/index.ts | 13 +- packages/theme/src/themes/default/index.ts | 11 ++ src/actions/index.js | 2 + src/actions/payment.js | 4 + src/components/layout/AppLayout.js | 2 + src/components/ui/FeatureList.js | 3 +- src/features/planSelection/actions.js | 4 - .../planSelection/components/PlanSelection.js | 2 +- .../containers/PlanSelectionScreen.js | 13 +- src/features/planSelection/store.js | 40 +----- src/features/trialStatusBar/actions.js | 13 ++ .../trialStatusBar/components/ProgressBar.js | 46 +++++++ .../trialStatusBar/components/TrialStatusBar.js | 135 +++++++++++++++++++++ .../containers/TrialStatusBarScreen.js | 101 +++++++++++++++ src/features/trialStatusBar/index.js | 30 +++++ src/features/trialStatusBar/store.js | 72 +++++++++++ src/i18n/locales/defaultMessages.json | 125 +++++++++++++++++-- src/i18n/locales/en-US.json | 7 ++ .../messages/src/components/layout/AppLayout.json | 12 +- .../trialStatusBar/components/TrialStatusBar.json | 41 +++++++ .../containers/TrialStatusBarScreen.json | 54 +++++++++ src/stores/AppStore.js | 14 +++ src/stores/FeaturesStore.js | 2 + src/stores/PaymentStore.js | 37 ++++++ src/stores/UserStore.js | 4 +- 25 files changed, 718 insertions(+), 69 deletions(-) create mode 100644 src/features/trialStatusBar/actions.js create mode 100644 src/features/trialStatusBar/components/ProgressBar.js create mode 100644 src/features/trialStatusBar/components/TrialStatusBar.js create mode 100644 src/features/trialStatusBar/containers/TrialStatusBarScreen.js create mode 100644 src/features/trialStatusBar/index.js create mode 100644 src/features/trialStatusBar/store.js create mode 100644 src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json create mode 100644 src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json (limited to 'src/stores/FeaturesStore.js') diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index 9a66f3463..30cc19d99 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts @@ -65,7 +65,7 @@ export const selectOptionItemHoverColor = selectColor; export const selectSearchColor = inputBackground; // Modal -export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.5).rgb().string(); +export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); export const colorModalBackground = legacyStyles.darkThemeGrayDark; // Services @@ -146,3 +146,14 @@ export const todos = merge({}, defaultStyles.todos, { background: legacyStyles.themeGrayLight, }, }); + +// TrialStatusBar +export const trialStatusBar = merge({}, defaultStyles.trialStatusBar, { + bar: { + background: legacyStyles.darkThemeGray, + }, + progressBar: { + background: legacyStyles.darkThemeGrayLighter, + progressIndicator: legacyStyles.darkThemeGrayLightest, + }, +}); diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts index 057fde72f..b484d9972 100644 --- a/packages/theme/src/themes/default/index.ts +++ b/packages/theme/src/themes/default/index.ts @@ -238,3 +238,14 @@ export const todos = { backgroundHover: styleTypes.primary.accent, }, }; + +// TrialStatusBar +export const trialStatusBar = { + bar: { + background: legacyStyles.themeGray, + }, + progressBar: { + background: legacyStyles.themeGrayLight, + progressIndicator: legacyStyles.themeGrayLighter, + }, +}; diff --git a/src/actions/index.js b/src/actions/index.js index 1c033fb96..9d3684edc 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -15,6 +15,7 @@ import announcements from '../features/announcements/actions'; import workspaces from '../features/workspaces/actions'; import todos from '../features/todos/actions'; import planSelection from '../features/planSelection/actions'; +import trialStatusBar from '../features/trialStatusBar/actions'; const actions = Object.assign({}, { service, @@ -35,4 +36,5 @@ export default Object.assign( { workspaces }, { todos }, { planSelection }, + { trialStatusBar }, ); diff --git a/src/actions/payment.js b/src/actions/payment.js index 2aaefc025..f61faf197 100644 --- a/src/actions/payment.js +++ b/src/actions/payment.js @@ -4,5 +4,9 @@ export default { createHostedPage: { planId: PropTypes.string.isRequired, }, + upgradeAccount: { + planId: PropTypes.string.isRequired, + onCloseWindow: PropTypes.func, + }, createDashboardUrl: {}, }; diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js index fe81b1911..9b110262a 100644 --- a/src/components/layout/AppLayout.js +++ b/src/components/layout/AppLayout.js @@ -20,6 +20,7 @@ import AppUpdateInfoBar from '../AppUpdateInfoBar'; import TrialActivationInfoBar from '../TrialActivationInfoBar'; import Todos from '../../features/todos/containers/TodosScreen'; import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen'; +import TrialStatusBar from '../../features/trialStatusBar/containers/TrialStatusBarScreen'; function createMarkup(HTMLString) { return { __html: HTMLString }; @@ -174,6 +175,7 @@ class AppLayout extends Component { {services} {children} + diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js index 732b40e40..7ba8b54d7 100644 --- a/src/components/ui/FeatureList.js +++ b/src/components/ui/FeatureList.js @@ -72,12 +72,13 @@ export class FeatureList extends Component { static propTypes = { className: PropTypes.string, featureClassName: PropTypes.string, - plan: PropTypes.oneOf(PLANS).isRequired, + plan: PropTypes.oneOf(PLANS), }; static defaultProps = { className: '', featureClassName: '', + plan: false, } static contextTypes = { diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js index 21aa38ace..83f58bfd7 100644 --- a/src/features/planSelection/actions.js +++ b/src/features/planSelection/actions.js @@ -2,10 +2,6 @@ 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); diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js index 1a45cf035..cf4474114 100644 --- a/src/features/planSelection/components/PlanSelection.js +++ b/src/features/planSelection/components/PlanSelection.js @@ -205,7 +205,7 @@ class PlanSelection extends Component { price={plans.pro.yearly.price} currency={currency} ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)} - upgrade={() => upgradeAccount(plans.personal.yearly.id)} + upgrade={() => upgradeAccount(plans.pro.yearly.id)} className={classes.featuredPlan} perUser bestValue diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js index dff9051d8..6e8cdbf47 100644 --- a/src/features/planSelection/containers/PlanSelectionScreen.js +++ b/src/features/planSelection/containers/PlanSelectionScreen.js @@ -43,13 +43,10 @@ class PlanSelectionScreen extends Component { } upgradeAccount(planId) { - const { upgradeAccount, hideOverlay } = this.props.actions.planSelection; + const { upgradeAccount } = this.props.actions.payment; upgradeAccount({ planId, - onCloseWindow: () => { - hideOverlay(); - }, }); } @@ -63,7 +60,7 @@ class PlanSelectionScreen extends Component { 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 { downgradeAccount, hideOverlay } = this.props.actions.planSelection; return ( @@ -102,7 +99,7 @@ class PlanSelectionScreen extends Component { downgradeAccount(); hideOverlay(); } else { - upgradeAccount(plans.personal.yearly.id); + this.upgradeAccount(plans.personal.yearly.id); gaEvent(GA_CATEGORY_PLAN_SELECTION, 'SelectPlan', 'Revoke'); } @@ -123,8 +120,10 @@ PlanSelectionScreen.wrappedComponent.propTypes = { user: PropTypes.instanceOf(UserStore).isRequired, }).isRequired, actions: PropTypes.shape({ - planSelection: PropTypes.shape({ + payment: PropTypes.shape({ upgradeAccount: PropTypes.func.isRequired, + }), + planSelection: PropTypes.shape({ downgradeAccount: PropTypes.func.isRequired, hideOverlay: PropTypes.func.isRequired, }), diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js index e229c37e5..0d4672722 100644 --- a/src/features/planSelection/store.js +++ b/src/features/planSelection/store.js @@ -42,7 +42,6 @@ export default class PlanSelectionStore extends FeatureStore { // ACTIONS this._registerActions(createActionBindings([ - [planSelectionActions.upgradeAccount, this._upgradeAccount], [planSelectionActions.downgradeAccount, this._downgradeAccount], [planSelectionActions.hideOverlay, this._hideOverlay], ])); @@ -64,47 +63,12 @@ export default class PlanSelectionStore extends FeatureStore { @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', () => { - this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); - this.stores.features.featuresRequest.invalidate({ immediately: true }); - - onCloseWindow(); - }); - }; - @action _downgradeAccount = () => { downgradeUserRequest.execute(); } @@ -112,4 +76,8 @@ export default class PlanSelectionStore extends FeatureStore { @action _hideOverlay = () => { this.hideOverlay = true; } + + @action _showOverlay = () => { + this.hideOverlay = false; + } } diff --git a/src/features/trialStatusBar/actions.js b/src/features/trialStatusBar/actions.js new file mode 100644 index 000000000..38df76458 --- /dev/null +++ b/src/features/trialStatusBar/actions.js @@ -0,0 +1,13 @@ +import PropTypes from 'prop-types'; +import { createActionsFromDefinitions } from '../../actions/lib/actions'; + +export const trialStatusBarActions = createActionsFromDefinitions({ + upgradeAccount: { + planId: PropTypes.string.isRequired, + onCloseWindow: PropTypes.func.isRequired, + }, + downgradeAccount: {}, + hideOverlay: {}, +}, PropTypes.checkPropTypes); + +export default trialStatusBarActions; diff --git a/src/features/trialStatusBar/components/ProgressBar.js b/src/features/trialStatusBar/components/ProgressBar.js new file mode 100644 index 000000000..80d478d8c --- /dev/null +++ b/src/features/trialStatusBar/components/ProgressBar.js @@ -0,0 +1,46 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; + +const styles = theme => ({ + root: { + background: theme.trialStatusBar.progressBar.background, + width: '25%', + maxWidth: 200, + height: 8, + display: 'flex', + alignItems: 'center', + borderRadius: theme.borderRadius, + overflow: 'hidden', + }, + progress: { + background: theme.trialStatusBar.progressBar.progressIndicator, + width: ({ percent }) => `${percent}%`, + height: '100%', + }, +}); + +@injectSheet(styles) @observer +class ProgressBar extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + percent: PropTypes.number.isRequired, + }; + + render() { + const { + classes, + } = this.props; + + return ( +
+
+
+ ); + } +} + +export default ProgressBar; diff --git a/src/features/trialStatusBar/components/TrialStatusBar.js b/src/features/trialStatusBar/components/TrialStatusBar.js new file mode 100644 index 000000000..b8fe4acc9 --- /dev/null +++ b/src/features/trialStatusBar/components/TrialStatusBar.js @@ -0,0 +1,135 @@ +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 { Icon } from '@meetfranz/ui'; +import { mdiArrowRight, mdiWindowClose } from '@mdi/js'; +import classnames from 'classnames'; + +import ProgressBar from './ProgressBar'; + +const messages = defineMessages({ + restTime: { + id: 'feature.trialStatusBar.restTime', + defaultMessage: '!!!Your Free Franz {plan} Trial ends in {time}.', + }, + expired: { + id: 'feature.trialStatusBar.expired', + defaultMessage: '!!!Your free Franz {plan} Trial has expired, please upgrade your account.', + }, + cta: { + id: 'feature.trialStatusBar.cta', + defaultMessage: '!!!Upgrade now', + }, +}); + +const styles = theme => ({ + root: { + background: theme.trialStatusBar.bar.background, + width: '100%', + height: 25, + order: 10, + display: 'flex', + alignItems: 'center', + fontSize: 12, + padding: [0, 10], + justifyContent: 'flex-end', + }, + ended: { + background: theme.styleTypes.warning.accent, + color: theme.styleTypes.warning.contrast, + }, + message: { + marginLeft: 20, + }, + action: { + marginLeft: 20, + fontSize: 12, + color: theme.colorText, + textDecoration: 'underline', + display: 'flex', + + '& svg': { + margin: [1, 2, 0, 0], + }, + }, +}); + +@injectSheet(styles) @observer +class TrialStatusBar extends Component { + static propTypes = { + planName: PropTypes.string.isRequired, + percent: PropTypes.number.isRequired, + upgradeAccount: PropTypes.func.isRequired, + hideOverlay: PropTypes.func.isRequired, + trialEnd: PropTypes.string.isRequired, + hasEnded: PropTypes.bool.isRequired, + classes: PropTypes.object.isRequired, + }; + + static contextTypes = { + intl: intlShape, + }; + + render() { + const { + planName, + percent, + upgradeAccount, + hideOverlay, + trialEnd, + hasEnded, + classes, + } = this.props; + + const { intl } = this.context; + + return ( +
+ + {' '} + + {!hasEnded ? ( + intl.formatMessage(messages.restTime, { + plan: planName, + time: trialEnd, + }) + ) : ( + intl.formatMessage(messages.expired, { + plan: planName, + }) + )} + + + +
+ ); + } +} + +export default TrialStatusBar; diff --git a/src/features/trialStatusBar/containers/TrialStatusBarScreen.js b/src/features/trialStatusBar/containers/TrialStatusBarScreen.js new file mode 100644 index 000000000..eb0aafaea --- /dev/null +++ b/src/features/trialStatusBar/containers/TrialStatusBarScreen.js @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; +import { observer, inject } from 'mobx-react'; +import PropTypes from 'prop-types'; +import ms from 'ms'; + +import FeaturesStore from '../../../stores/FeaturesStore'; +import UserStore from '../../../stores/UserStore'; +import TrialStatusBar from '../components/TrialStatusBar'; +import ErrorBoundary from '../../../components/util/ErrorBoundary'; +import { trialStatusBarStore } from '..'; + +@inject('stores', 'actions') @observer +class TrialStatusBarScreen extends Component { + state = { + showOverlay: true, + percent: 0, + restTime: '', + hasEnded: false, + }; + + percentInterval = null; + + componentDidMount() { + this.percentInterval = setInterval(() => { + this.calculateRestTime(); + }, ms('1m')); + + this.calculateRestTime(); + } + + componentWillUnmount() { + clearInterval(this.percentInterval); + } + + calculateRestTime() { + const { trialEndTime } = trialStatusBarStore; + const percent = Math.abs(100 - Math.abs(trialEndTime.asMilliseconds()) * 100 / ms('14d')).toFixed(2); + const restTime = trialEndTime.humanize(); + const hasEnded = trialEndTime.asMilliseconds() > 0; + + this.setState({ + percent, + restTime, + hasEnded, + }); + } + + hideOverlay() { + this.setState({ + showOverlay: false, + }); + } + + + render() { + const { + showOverlay, + percent, + restTime, + hasEnded, + } = this.state; + + if (!trialStatusBarStore || !trialStatusBarStore.isFeatureActive || !showOverlay || !trialStatusBarStore.showTrialStatusBarOverlay) { + return null; + } + + const { user } = this.props.stores; + const { upgradeAccount } = this.props.actions.payment; + + console.log('hasEnded', hasEnded); + + return ( + + upgradeAccount({ + planId: user.team.plan, + })} + hideOverlay={() => this.hideOverlay()} + hasEnded={hasEnded} + /> + + ); + } +} + +export default TrialStatusBarScreen; + +TrialStatusBarScreen.wrappedComponent.propTypes = { + stores: PropTypes.shape({ + features: PropTypes.instanceOf(FeaturesStore).isRequired, + user: PropTypes.instanceOf(UserStore).isRequired, + }).isRequired, + actions: PropTypes.shape({ + payment: PropTypes.shape({ + upgradeAccount: PropTypes.func.isRequired, + }), + }).isRequired, +}; diff --git a/src/features/trialStatusBar/index.js b/src/features/trialStatusBar/index.js new file mode 100644 index 000000000..ec84cdfd7 --- /dev/null +++ b/src/features/trialStatusBar/index.js @@ -0,0 +1,30 @@ +import { reaction } from 'mobx'; +import TrialStatusBarStore from './store'; + +const debug = require('debug')('Franz:feature:trialStatusBar'); + +export const GA_CATEGORY_TRIAL_STATUS_BAR = 'trialStatusBar'; + +export const trialStatusBarStore = new TrialStatusBarStore(); + +export default function initTrialStatusBar(stores, actions) { + stores.trialStatusBar = trialStatusBarStore; + const { features } = stores; + + // Toggle trialStatusBar feature + reaction( + () => features.features.isTrialStatusBarEnabled, + (isEnabled) => { + if (isEnabled) { + debug('Initializing `trialStatusBar` feature'); + trialStatusBarStore.start(stores, actions); + } else if (trialStatusBarStore.isFeatureActive) { + debug('Disabling `trialStatusBar` feature'); + trialStatusBarStore.stop(); + } + }, + { + fireImmediately: true, + }, + ); +} diff --git a/src/features/trialStatusBar/store.js b/src/features/trialStatusBar/store.js new file mode 100644 index 000000000..89cf32392 --- /dev/null +++ b/src/features/trialStatusBar/store.js @@ -0,0 +1,72 @@ +import { + action, + observable, + computed, +} from 'mobx'; +import moment from 'moment'; + +import { trialStatusBarActions } from './actions'; +import { FeatureStore } from '../utils/FeatureStore'; +import { createActionBindings } from '../utils/ActionBinding'; + +const debug = require('debug')('Franz:feature:trialStatusBar:store'); + +export default class TrialStatusBarStore extends FeatureStore { + @observable isFeatureActive = false; + + @observable isFeatureEnabled = false; + + @computed get showTrialStatusBarOverlay() { + if (this.isFeatureActive) { + const { team } = this.stores.user; + if (team && !this.hideOverlay) { + return team.state !== 'expired' && team.isTrial; + } + } + + return false; + } + + @computed get trialEndTime() { + if (this.isFeatureActive) { + const { team } = this.stores.user; + + if (team && !this.hideOverlay) { + return moment.duration(moment().diff(team.trialEnd)); + } + } + + return moment.duration(); + } + + // ========== PUBLIC API ========= // + + @action start(stores, actions, api) { + debug('TrialStatusBarStore::start'); + this.stores = stores; + this.actions = actions; + this.api = api; + + // ACTIONS + + this._registerActions(createActionBindings([ + [trialStatusBarActions.hideOverlay, this._hideOverlay], + ])); + + this.isFeatureActive = true; + } + + @action stop() { + super.stop(); + debug('TrialStatusBarStore::stop'); + this.isFeatureActive = false; + } + + // ========== PRIVATE METHODS ========= // + + // Actions + + @action _hideOverlay = () => { + this.hideOverlay = true; + } +} diff --git a/src/i18n/locales/defaultMessages.json b/src/i18n/locales/defaultMessages.json index eafac1f87..98f37cf8a 100644 --- a/src/i18n/locales/defaultMessages.json +++ b/src/i18n/locales/defaultMessages.json @@ -734,39 +734,39 @@ "defaultMessage": "!!!Your services have been updated.", "end": { "column": 3, - "line": 32 + "line": 33 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.servicesUpdated", "start": { "column": 19, - "line": 29 + "line": 30 } }, { "defaultMessage": "!!!Reload services", "end": { "column": 3, - "line": 36 + "line": 37 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.buttonReloadServices", "start": { "column": 24, - "line": 33 + "line": 34 } }, { "defaultMessage": "!!!Could not load services and user information", "end": { "column": 3, - "line": 40 + "line": 41 }, "file": "src/components/layout/AppLayout.js", "id": "infobar.requiredRequestsFailed", "start": { "column": 26, - "line": 37 + "line": 38 } } ], @@ -3915,39 +3915,39 @@ "defaultMessage": "!!!per month", "end": { "column": 3, - "line": 22 + "line": 18 }, "file": "src/features/planSelection/components/PlanItem.js", "id": "subscription.interval.perMonth", "start": { "column": 12, - "line": 19 + "line": 15 } }, { "defaultMessage": "!!!per month & user", "end": { "column": 3, - "line": 26 + "line": 22 }, "file": "src/features/planSelection/components/PlanItem.js", "id": "subscription.interval.perMonthPerUser", "start": { "column": 19, - "line": 23 + "line": 19 } }, { "defaultMessage": "!!!Best value", "end": { "column": 3, - "line": 30 + "line": 26 }, "file": "src/features/planSelection/components/PlanItem.js", "id": "subscription.bestValue", "start": { "column": 13, - "line": 27 + "line": 23 } } ], @@ -4378,6 +4378,107 @@ ], "path": "src/features/todos/components/TodosWebview.json" }, + { + "descriptors": [ + { + "defaultMessage": "!!!Your Free Franz {plan} Trial ends in {time}.", + "end": { + "column": 3, + "line": 16 + }, + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "id": "feature.trialStatusBar.restTime", + "start": { + "column": 12, + "line": 13 + } + }, + { + "defaultMessage": "!!!Your free Franz {plan} Trial has expired, please upgrade your account.", + "end": { + "column": 3, + "line": 20 + }, + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "id": "feature.trialStatusBar.expired", + "start": { + "column": 11, + "line": 17 + } + }, + { + "defaultMessage": "!!!Upgrade now", + "end": { + "column": 3, + "line": 24 + }, + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "id": "feature.trialStatusBar.cta", + "start": { + "column": 7, + "line": 21 + } + } + ], + "path": "src/features/trialStatusBar/components/TrialStatusBar.json" + }, + { + "descriptors": [ + { + "defaultMessage": "!!!Downgrade your Franz Plan", + "end": { + "column": 3, + "line": 19 + }, + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "id": "feature.trialStatusBar.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/trialStatusBar/containers/TrialStatusBarScreen.js", + "id": "feature.trialStatusBar.fullscreen.dialog.message", + "start": { + "column": 17, + "line": 20 + } + }, + { + "defaultMessage": "!!!Downgrade to Free", + "end": { + "column": 3, + "line": 27 + }, + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "id": "feature.trialStatusBar.fullscreen.dialog.cta.downgrade", + "start": { + "column": 22, + "line": 24 + } + }, + { + "defaultMessage": "!!!Choose Personal", + "end": { + "column": 3, + "line": 31 + }, + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "id": "feature.trialStatusBar.fullscreen.dialog.cta.upgrade", + "start": { + "column": 20, + "line": 28 + } + } + ], + "path": "src/features/trialStatusBar/containers/TrialStatusBarScreen.json" + }, { "descriptors": [ { diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 6977ec096..1ba91bdfa 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json @@ -35,6 +35,13 @@ "feature.todos.premium.info": "Franz Todos are available to premium users now!", "feature.todos.premium.rollout": "Everyone else will have to wait a little longer.", "feature.todos.premium.upgrade": "Upgrade Account", + "feature.trialStatusBar.cta": "Upgrade now", + "feature.trialStatusBar.expired": "Your free Franz {plan} Trial has expired, please upgrade your account.", + "feature.trialStatusBar.fullscreen.dialog.cta.downgrade": "Downgrade to Free", + "feature.trialStatusBar.fullscreen.dialog.cta.upgrade": "Choose Personal", + "feature.trialStatusBar.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.trialStatusBar.fullscreen.dialog.title": "Downgrade your Franz Plan", + "feature.trialStatusBar.restTime": "Your Free Franz {plan} Trial ends in {time}.", "global.api.unhealthy": "Can't connect to Franz online services", "global.franzProRequired": "Franz Professional Required", "global.notConnectedToTheInternet": "You are not connected to the internet.", diff --git a/src/i18n/messages/src/components/layout/AppLayout.json b/src/i18n/messages/src/components/layout/AppLayout.json index 22f11cedd..95da24042 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": 29, + "line": 30, "column": 19 }, "end": { - "line": 32, + "line": 33, "column": 3 } }, @@ -17,11 +17,11 @@ "defaultMessage": "!!!Reload services", "file": "src/components/layout/AppLayout.js", "start": { - "line": 33, + "line": 34, "column": 24 }, "end": { - "line": 36, + "line": 37, "column": 3 } }, @@ -30,11 +30,11 @@ "defaultMessage": "!!!Could not load services and user information", "file": "src/components/layout/AppLayout.js", "start": { - "line": 37, + "line": 38, "column": 26 }, "end": { - "line": 40, + "line": 41, "column": 3 } } diff --git a/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json b/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json new file mode 100644 index 000000000..bf211a016 --- /dev/null +++ b/src/i18n/messages/src/features/trialStatusBar/components/TrialStatusBar.json @@ -0,0 +1,41 @@ +[ + { + "id": "feature.trialStatusBar.restTime", + "defaultMessage": "!!!Your Free Franz {plan} Trial ends in {time}.", + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "start": { + "line": 13, + "column": 12 + }, + "end": { + "line": 16, + "column": 3 + } + }, + { + "id": "feature.trialStatusBar.expired", + "defaultMessage": "!!!Your free Franz {plan} Trial has expired, please upgrade your account.", + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "start": { + "line": 17, + "column": 11 + }, + "end": { + "line": 20, + "column": 3 + } + }, + { + "id": "feature.trialStatusBar.cta", + "defaultMessage": "!!!Upgrade now", + "file": "src/features/trialStatusBar/components/TrialStatusBar.js", + "start": { + "line": 21, + "column": 7 + }, + "end": { + "line": 24, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json b/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json new file mode 100644 index 000000000..306cd0fee --- /dev/null +++ b/src/i18n/messages/src/features/trialStatusBar/containers/TrialStatusBarScreen.json @@ -0,0 +1,54 @@ +[ + { + "id": "feature.trialStatusBar.fullscreen.dialog.title", + "defaultMessage": "!!!Downgrade your Franz Plan", + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "start": { + "line": 16, + "column": 15 + }, + "end": { + "line": 19, + "column": 3 + } + }, + { + "id": "feature.trialStatusBar.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/trialStatusBar/containers/TrialStatusBarScreen.js", + "start": { + "line": 20, + "column": 17 + }, + "end": { + "line": 23, + "column": 3 + } + }, + { + "id": "feature.trialStatusBar.fullscreen.dialog.cta.downgrade", + "defaultMessage": "!!!Downgrade to Free", + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "start": { + "line": 24, + "column": 22 + }, + "end": { + "line": 27, + "column": 3 + } + }, + { + "id": "feature.trialStatusBar.fullscreen.dialog.cta.upgrade", + "defaultMessage": "!!!Choose Personal", + "file": "src/features/trialStatusBar/containers/TrialStatusBarScreen.js", + "start": { + "line": 28, + "column": 20 + }, + "end": { + "line": 31, + "column": 3 + } + } +] \ No newline at end of file diff --git a/src/stores/AppStore.js b/src/stores/AppStore.js index f102fc370..329c43f32 100644 --- a/src/stores/AppStore.js +++ b/src/stores/AppStore.js @@ -81,6 +81,8 @@ export default class AppStore extends Store { dictionaries = []; + fetchDataInterval = null; + constructor(...args) { super(...args); @@ -102,6 +104,7 @@ export default class AppStore extends Store { this._setLocale.bind(this), this._muteAppHandler.bind(this), this._handleFullScreen.bind(this), + this._handleLogout.bind(this), ]); } @@ -129,6 +132,11 @@ export default class AppStore extends Store { this._systemDND(); setInterval(() => this._systemDND(), ms('5s')); + this.fetchDataInterval = setInterval(() => { + this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); + this.stores.features.featuresRequest.invalidate({ immediately: true }); + }, ms('10s')); + // Check for updates once every 4 hours setInterval(() => this._checkForUpdates(), CHECK_INTERVAL); // Check for an update in 30s (need a delay to prevent Squirrel Installer lock file issues) @@ -430,6 +438,12 @@ export default class AppStore extends Store { } } + _handleLogout() { + if (!this.stores.user.isLoggedIn) { + clearInterval(this.fetchDataInterval); + } + } + // Helpers _appStartsCounter() { this.actions.settings.update({ diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js index bffcb01bc..5d379fd3e 100644 --- a/src/stores/FeaturesStore.js +++ b/src/stores/FeaturesStore.js @@ -20,6 +20,7 @@ import serviceLimit from '../features/serviceLimit'; import communityRecipes from '../features/communityRecipes'; import todos from '../features/todos'; import planSelection from '../features/planSelection'; +import trialStatusBar from '../features/trialStatusBar'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -83,5 +84,6 @@ export default class FeaturesStore extends Store { communityRecipes(this.stores, this.actions); todos(this.stores, this.actions); planSelection(this.stores, this.actions); + trialStatusBar(this.stores, this.actions); } } diff --git a/src/stores/PaymentStore.js b/src/stores/PaymentStore.js index d4de476c8..b90e8f006 100644 --- a/src/stores/PaymentStore.js +++ b/src/stores/PaymentStore.js @@ -1,10 +1,13 @@ import { action, observable, computed } from 'mobx'; +import { remote } from 'electron'; import Store from './lib/Store'; import CachedRequest from './lib/CachedRequest'; import Request from './lib/Request'; import { gaEvent } from '../lib/analytics'; +const { BrowserWindow } = remote; + export default class PaymentStore extends Store { @observable plansRequest = new CachedRequest(this.api.payment, 'plans'); @@ -14,6 +17,7 @@ export default class PaymentStore extends Store { super(...args); this.actions.payment.createHostedPage.listen(this._createHostedPage.bind(this)); + this.actions.payment.upgradeAccount.listen(this._upgradeAccount.bind(this)); } @computed get plan() { @@ -30,4 +34,37 @@ export default class PaymentStore extends Store { return request; } + + @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', () => { + this.stores.user.getUserInfoRequest.invalidate({ immediately: true }); + this.stores.features.featuresRequest.invalidate({ immediately: true }); + + onCloseWindow(); + }); + } } diff --git a/src/stores/UserStore.js b/src/stores/UserStore.js index b652098f9..735e8f886 100644 --- a/src/stores/UserStore.js +++ b/src/stores/UserStore.js @@ -77,6 +77,8 @@ export default class UserStore extends Store { @observable logoutReason = null; + fetchUserInfoInterval = null; + constructor(...args) { super(...args); @@ -161,7 +163,7 @@ export default class UserStore extends Store { } @computed get isPremiumOverride() { - return ((!this.team || !this.team.plan) && this.isPremium) || (this.team.state === 'expired' && this.isPremium); + return ((!this.team || !this.team.plan) && this.isPremium) || (this.team && this.team.state === 'expired' && this.isPremium); } @computed get isPersonal() { -- cgit v1.2.3-70-g09d2 From b6276bd0cb88ce681bc35a6fc7b1ae0cf6ac56ea Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Thu, 17 Oct 2019 14:12:36 +0200 Subject: optimize trial signup flow --- src/actions/user.js | 4 +- src/api/server/ServerApi.js | 2 +- src/components/auth/Pricing.js | 92 +++++++++++++++------ src/containers/auth/PricingScreen.js | 17 ++-- src/containers/auth/SignupScreen.js | 22 ++++- src/i18n/locales/defaultMessages.json | 94 +++++++++++++++++----- src/i18n/locales/en-US.json | 6 +- src/i18n/messages/src/components/auth/Pricing.json | 81 ++++++++++++++----- src/stores/FeaturesStore.js | 1 + src/stores/UserStore.js | 4 +- 10 files changed, 245 insertions(+), 78 deletions(-) (limited to 'src/stores/FeaturesStore.js') diff --git a/src/actions/user.js b/src/actions/user.js index 5d7d9a899..7061a367a 100644 --- a/src/actions/user.js +++ b/src/actions/user.js @@ -11,8 +11,10 @@ export default { lastname: PropTypes.string.isRequired, email: PropTypes.string.isRequired, password: PropTypes.string.isRequired, - accountType: PropTypes.string.isRequired, + accountType: PropTypes.string, company: PropTypes.string, + plan: PropTypes.string, + currency: PropTypes.string, }, retrievePassword: { email: PropTypes.string.isRequired, diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index f56c7b6e4..1f538368d 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js @@ -87,7 +87,7 @@ export default class ServerApi { } const trial = await request.json(); - debug('ServerApi::signup resolves', trial); + debug('ServerApi::activateTrial resolves', trial); return true; } diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index 67af04470..86b6a4263 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js @@ -13,12 +13,20 @@ import { FeatureList } from '../ui/FeatureList'; const messages = defineMessages({ headline: { - id: 'pricing.trial.headline', - defaultMessage: '!!!Franz Professional', + id: 'pricing.trial.headline.pro', + defaultMessage: '!!!Hi {name}, welcome to Franz', }, - personalOffer: { - id: 'pricing.trial.subheadline', - defaultMessage: '!!!Here\'s a special welcome for you:', + specialTreat: { + id: 'pricing.trial.intro.specialTreat', + defaultMessage: '!!!We have a special treat for you.', + }, + tryPro: { + id: 'pricing.trial.intro.tryPro', + defaultMessage: '!!!Enjoy the full Franz Professional experience completely free for 14 days.', + }, + happyMessaging: { + id: 'pricing.trial.intro.happyMessaging', + defaultMessage: '!!!Happy messaging,', }, noStringsAttachedHeadline: { id: 'pricing.trial.terms.headline', @@ -44,6 +52,10 @@ const messages = defineMessages({ id: 'pricing.trial.cta.accept', defaultMessage: '!!!Start my 14-day Franz Professional Trial ', }, + ctaStart: { + id: 'pricing.trial.cta.start', + defaultMessage: '!!!Start using Franz', + }, ctaSkip: { id: 'pricing.trial.cta.skip', defaultMessage: '!!!Continue to Franz', @@ -98,6 +110,34 @@ const styles = theme => ({ margin: [20, 0, 0], color: theme.styleTypes.danger.accent, }, + priceContainer: { + display: 'flex', + justifyContent: 'space-evenly', + margin: [10, 0, 15], + }, + price: { + '& sup': { + verticalAlign: 14, + fontSize: 20, + }, + }, + figure: { + fontSize: 40, + }, + regularPrice: { + position: 'relative', + + '&:before': { + content: '" "', + position: 'absolute', + width: '130%', + height: 1, + top: 14, + left: -12, + borderBottom: [3, 'solid', 'red'], + transform: 'rotateZ(-20deg)', + }, + }, }); export default @observer @injectSheet(styles) class Signup extends Component { @@ -129,6 +169,8 @@ export default @observer @injectSheet(styles) class Signup extends Component { } = this.props; const { intl } = this.context; + const [intPart, fractionPart] = (price).toString().split('.'); + return (
@@ -140,25 +182,39 @@ export default @observer @injectSheet(styles) class Signup extends Component { alt="" /> )} -

{intl.formatMessage(messages.personalOffer)}

-

{intl.formatMessage(messages.headline)}

+

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

- We built Franz with a lot of effort, manpower and love, - to boost up your messaging experience. + {intl.formatMessage(messages.specialTreat)}

- For the next 14 days, we are going to give you the full Franz Professional experience so you can watch your communication evolve! + {intl.formatMessage(messages.tryPro)}

- Thanks for being a hero. + {intl.formatMessage(messages.happyMessaging)}

Stefan Malzner

+
+

+ + {currency} + {intPart} + + {fractionPart} +

+

+ + {currency} + 0 + + 00 +

+

{intl.formatMessage(messages.noStringsAttachedHeadline)} @@ -179,7 +235,7 @@ export default @observer @injectSheet(styles) class Signup extends Component {

{intl.formatMessage(messages.activationError)}

)}

- {/*
    - - - - - - - - - - -
*/}
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