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 --- src/stores/UserStore.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/stores/UserStore.js') 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/UserStore.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