aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2019-07-04 15:54:27 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-07-04 15:54:27 +0200
commit268db27162e8d2cd0252b1be9bf69006cf6323ca (patch)
tree6ca47ee4159ba79f88d8848ac2ba9a8a8a7700da /src/components
parentMerge branch 'release/5.2.0-beta.4' into feature/new-pricing (diff)
downloadferdium-app-268db27162e8d2cd0252b1be9bf69006cf6323ca.tar.gz
ferdium-app-268db27162e8d2cd0252b1be9bf69006cf6323ca.tar.zst
ferdium-app-268db27162e8d2cd0252b1be9bf69006cf6323ca.zip
Add trial onboarding during signup
Diffstat (limited to 'src/components')
-rw-r--r--src/components/auth/Pricing.js246
-rw-r--r--src/components/services/content/Services.js80
-rw-r--r--src/components/ui/FeatureItem.js36
-rw-r--r--src/components/ui/FeatureList.js89
4 files changed, 341 insertions, 110 deletions
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
index 7ab14f429..d20779025 100644
--- a/src/components/auth/Pricing.js
+++ b/src/components/auth/Pricing.js
@@ -1,40 +1,107 @@
1import React, { Component } from 'react'; 1import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5// import { Link } from 'react-router'; 5import injectSheet from 'react-jss';
6import { H2, Loader } from '@meetfranz/ui';
7import classnames from 'classnames';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureItem } from '../ui/FeatureItem';
11import FeatureList from '../ui/FeatureList';
6 12
7// import Button from '../ui/Button';
8import Loader from '../ui/Loader';
9import Appear from '../ui/effects/Appear';
10import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen';
11 13
12const messages = defineMessages({ 14const messages = defineMessages({
13 headline: { 15 headline: {
14 id: 'pricing.headline', 16 id: 'pricing.trial.headline',
15 defaultMessage: '!!!Support Franz', 17 defaultMessage: '!!!Franz Professional',
18 },
19 personalOffer: {
20 id: 'pricing.trial.subheadline',
21 defaultMessage: '!!!Your personal welcome offer:',
22 },
23 noStringsAttachedHeadline: {
24 id: 'pricing.trial.terms.headline',
25 defaultMessage: '!!!No strings attached',
26 },
27 noCreditCard: {
28 id: 'pricing.trial.terms.noCreditCard',
29 defaultMessage: '!!!No credit card required',
16 }, 30 },
17 monthlySupportLabel: { 31 automaticTrialEnd: {
18 id: 'pricing.support.label', 32 id: 'pricing.trial.terms.automaticTrialEnd',
19 defaultMessage: '!!!Select your support plan', 33 defaultMessage: '!!!Your free trial ends automatically after 14 days',
20 }, 34 },
21 submitButtonLabel: { 35 activationError: {
22 id: 'pricing.submit.label', 36 id: 'pricing.trial.error',
23 defaultMessage: '!!!Support the development of Franz', 37 defaultMessage: '!!!Sorry, we could not activate your trial!',
24 }, 38 },
25 skipPayment: { 39 ctaAccept: {
26 id: 'pricing.link.skipPayment', 40 id: 'pricing.trial.cta.accept',
27 defaultMessage: '!!!I don\'t want to support the development of Franz.', 41 defaultMessage: '!!!Yes, upgrade my account to Franz Professional',
42 },
43 ctaSkip: {
44 id: 'pricing.trial.cta.skip',
45 defaultMessage: '!!!Continue to Franz',
46 },
47 featuresHeadline: {
48 id: 'pricing.trial.features.headline',
49 defaultMessage: '!!!Franz Professional includes:',
28 }, 50 },
29}); 51});
30 52
31export default @observer class Signup extends Component { 53const styles = theme => ({
54 container: {
55 position: 'relative',
56 marginLeft: -150,
57 },
58 welcomeOffer: {
59 textAlign: 'center',
60 fontWeight: 'bold',
61 },
62 keyTerms: {
63 textAlign: 'center',
64 },
65 content: {
66 position: 'relative',
67 zIndex: 20,
68 },
69 featureContainer: {
70 width: 300,
71 position: 'absolute',
72 left: 'calc(100% / 2 + 225px)',
73 top: 155,
74 background: theme.signup.pricing.feature.background,
75 height: 'auto',
76 padding: 20,
77 borderTopRightRadius: theme.borderRadius,
78 borderBottomRightRadius: theme.borderRadius,
79 zIndex: 10,
80 },
81 featureItem: {
82 borderBottom: [1, 'solid', theme.signup.pricing.feature.border],
83 },
84 cta: {
85 marginTop: 40,
86 width: '100%',
87 },
88 skipLink: {
89 textAlign: 'center',
90 marginTop: 10,
91 },
92 error: {
93 margin: [20, 0, 0],
94 color: theme.styleTypes.danger.accent,
95 },
96});
97
98export default @observer @injectSheet(styles) class Signup extends Component {
32 static propTypes = { 99 static propTypes = {
33 donor: MobxPropTypes.objectOrObservableObject.isRequired, 100 onSubmit: PropTypes.func.isRequired,
34 isLoading: PropTypes.bool.isRequired, 101 isLoadingRequiredData: PropTypes.bool.isRequired,
35 isLoadingUser: PropTypes.bool.isRequired, 102 isActivatingTrial: PropTypes.bool.isRequired,
36 onCloseSubscriptionWindow: PropTypes.func.isRequired, 103 trialActivationError: PropTypes.bool.isRequired,
37 skipAction: PropTypes.func.isRequired, 104 classes: PropTypes.object.isRequired,
38 }; 105 };
39 106
40 static contextTypes = { 107 static contextTypes = {
@@ -43,70 +110,37 @@ export default @observer class Signup extends Component {
43 110
44 render() { 111 render() {
45 const { 112 const {
46 donor, 113 onSubmit,
47 isLoading, 114 isLoadingRequiredData,
48 isLoadingUser, 115 isActivatingTrial,
49 onCloseSubscriptionWindow, 116 trialActivationError,
50 skipAction, 117 classes,
51 } = this.props; 118 } = this.props;
52 const { intl } = this.context; 119 const { intl } = this.context;
53 120
54 return ( 121 return (
55 <div className="auth__scroll-container"> 122 <div className={classnames('auth__scroll-container', classes.container)}>
56 <div className="auth__container auth__container--signup"> 123 <div className={classnames('auth__container', 'auth__container--signup', classes.content)}>
57 <form className="franz-form auth__form"> 124 <form className="franz-form auth__form">
58 <img 125 {isLoadingRequiredData ? <Loader /> : (
59 src="./assets/images/sm.png" 126 <img
60 className="auth__logo auth__logo--sm" 127 src="./assets/images/sm.png"
61 alt="" 128 className="auth__logo auth__logo--sm"
62 /> 129 alt=""
130 />
131 )}
132 <p className={classes.welcomeOffer}>{intl.formatMessage(messages.personalOffer)}</p>
63 <h1>{intl.formatMessage(messages.headline)}</h1> 133 <h1>{intl.formatMessage(messages.headline)}</h1>
64 <div className="auth__letter"> 134 <div className="auth__letter">
65 {isLoadingUser && ( 135 <p>
66 <p>Loading</p> 136 We built Franz with a lot of effort, manpower and love,
67 )} 137 to boost up your messaging experience.
68 {!isLoadingUser && ( 138 <br />
69 donor.amount ? ( 139 </p>
70 <span> 140 <p>
71 <p> 141 Get the free 14 day Franz Professional trial and see your communication evolving.
72 Thank you so much for your previous donation of 142 <br />
73 {' '} 143 </p>
74 <strong>
75 $
76 {donor.amount}
77 </strong>
78 .
79 <br />
80 Your support allowed us to get where we are today.
81 <br />
82 </p>
83 <p>
84 As an early supporter, you get
85 {' '}
86 <strong>a lifetime premium supporter license</strong>
87 {' '}
88 without any
89 additional charges.
90 </p>
91 <p>
92 However, If you want to keep supporting us, you are more than welcome to subscribe to a plan.
93 <br />
94 <br />
95 </p>
96 </span>
97 ) : (
98 <span>
99 <p>
100 We built Franz with a lot of effort, manpower and love,
101 to bring you the best messaging experience.
102 <br />
103 </p>
104 <p>
105 Getting a Franz Premium Supporter License will allow us to keep improving Franz for you.
106 </p>
107 </span>
108 )
109 )}
110 <p> 144 <p>
111 Thanks for being a hero. 145 Thanks for being a hero.
112 </p> 146 </p>
@@ -114,20 +148,48 @@ export default @observer class Signup extends Component {
114 <strong>Stefan Malzner</strong> 148 <strong>Stefan Malzner</strong>
115 </p> 149 </p>
116 </div> 150 </div>
117 <Loader loaded={!isLoading}> 151 <div className={classes.keyTerms}>
118 <Appear transitionName="slideDown"> 152 <H2>
119 <span className="label">{intl.formatMessage(messages.monthlySupportLabel)}</span> 153 {intl.formatMessage(messages.noStringsAttachedHeadline)}
120 <SubscriptionForm 154 </H2>
121 onCloseWindow={onCloseSubscriptionWindow} 155 <ul className={classes.keyTermsList}>
122 showSkipOption 156 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
123 skipAction={skipAction} 157 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
124 hideInfo={Boolean(donor.amount)} 158 </ul>
125 skipButtonLabel={intl.formatMessage(messages.skipPayment)} 159 </div>
126 /> 160 {trialActivationError && (
127 </Appear> 161 <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p>
128 </Loader> 162 )}
163 <Button
164 label={intl.formatMessage(messages.ctaAccept)}
165 className={classes.cta}
166 onClick={onSubmit}
167 busy={isActivatingTrial}
168 disabled={isLoadingRequiredData || isActivatingTrial}
169 />
170 <p className={classes.skipLink}>
171 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a>
172 </p>
129 </form> 173 </form>
130 </div> 174 </div>
175 <div className={classes.featureContainer}>
176 <H2>
177 {intl.formatMessage(messages.featuresHeadline)}
178 </H2>
179 {/* <ul className={classes.features}>
180 <FeatureItem name="Add unlimited services" className={classes.featureItem} />
181 <FeatureItem name="Spellchecker support" className={classes.featureItem} />
182 <FeatureItem name="Workspaces" className={classes.featureItem} />
183 <FeatureItem name="Add Custom Websites" className={classes.featureItem} />
184 <FeatureItem name="On-premise & other Hosted Services" className={classes.featureItem} />
185 <FeatureItem name="Install 3rd party services" className={classes.featureItem} />
186 <FeatureItem name="Service Proxies" className={classes.featureItem} />
187 <FeatureItem name="Team Management" className={classes.featureItem} />
188 <FeatureItem name="No Waiting Screens" className={classes.featureItem} />
189 <FeatureItem name="Forever ad-free" className={classes.featureItem} />
190 </ul> */}
191 <FeatureList />
192 </div>
131 </div> 193 </div>
132 ); 194 );
133 } 195 }
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index 48de0ebaa..7f1624003 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { Link } from 'react-router'; 4import { Link } from 'react-router';
5import { defineMessages, intlShape } from 'react-intl'; 5import { defineMessages, intlShape } from 'react-intl';
6import Confetti from 'react-confetti';
7import ms from 'ms';
8import injectSheet from 'react-jss';
6 9
7import ServiceView from './ServiceView'; 10import ServiceView from './ServiceView';
8import Appear from '../../ui/effects/Appear'; 11import Appear from '../../ui/effects/Appear';
@@ -18,7 +21,16 @@ const messages = defineMessages({
18 }, 21 },
19}); 22});
20 23
21export default @observer class Services extends Component { 24
25const styles = {
26 confettiContainer: {
27 position: 'absolute',
28 width: '100%',
29 zIndex: 0,
30 },
31};
32
33export default @observer @injectSheet(styles) class Services extends Component {
22 static propTypes = { 34 static propTypes = {
23 services: MobxPropTypes.arrayOrObservableArray, 35 services: MobxPropTypes.arrayOrObservableArray,
24 setWebviewReference: PropTypes.func.isRequired, 36 setWebviewReference: PropTypes.func.isRequired,
@@ -28,6 +40,8 @@ export default @observer class Services extends Component {
28 reload: PropTypes.func.isRequired, 40 reload: PropTypes.func.isRequired,
29 openSettings: PropTypes.func.isRequired, 41 openSettings: PropTypes.func.isRequired,
30 update: PropTypes.func.isRequired, 42 update: PropTypes.func.isRequired,
43 userHasCompletedSignup: PropTypes.bool.isRequired,
44 classes: PropTypes.object.isRequired,
31 }; 45 };
32 46
33 static defaultProps = { 47 static defaultProps = {
@@ -38,6 +52,18 @@ export default @observer class Services extends Component {
38 intl: intlShape, 52 intl: intlShape,
39 }; 53 };
40 54
55 state = {
56 showConfetti: true,
57 }
58
59 componentDidMount() {
60 window.setTimeout(() => {
61 this.setState({
62 showConfetti: false,
63 });
64 }, ms('8s'));
65 }
66
41 render() { 67 render() {
42 const { 68 const {
43 services, 69 services,
@@ -48,29 +74,47 @@ export default @observer class Services extends Component {
48 reload, 74 reload,
49 openSettings, 75 openSettings,
50 update, 76 update,
77 userHasCompletedSignup,
78 classes,
51 } = this.props; 79 } = this.props;
80
81 const {
82 showConfetti,
83 } = this.state;
84
52 const { intl } = this.context; 85 const { intl } = this.context;
53 86
54 return ( 87 return (
55 <div className="services"> 88 <div className="services">
56 {services.length === 0 && ( 89 {services.length === 0 && (
57 <Appear 90 <>
58 timeout={1500} 91 {userHasCompletedSignup && (
59 transitionName="slideUp" 92 <div className={classes.confettiContainer}>
60 > 93 <Confetti
61 <div className="services__no-service"> 94 width={window.width}
62 <img src="./assets/images/logo.svg" alt="" /> 95 height={window.height}
63 <h1>{intl.formatMessage(messages.welcome)}</h1> 96 numberOfPieces={showConfetti ? 200 : 0}
64 <Appear 97 />
65 timeout={300} 98 </div>
66 transitionName="slideUp" 99 )}
67 > 100 <Appear
68 <Link to="/settings/recipes" className="button"> 101 timeout={1500}
69 {intl.formatMessage(messages.getStarted)} 102 transitionName="slideUp"
70 </Link> 103 >
71 </Appear> 104 <div className="services__no-service">
72 </div> 105 <img src="./assets/images/logo.svg" alt="" />
73 </Appear> 106 <h1>{intl.formatMessage(messages.welcome)}</h1>
107 <Appear
108 timeout={300}
109 transitionName="slideUp"
110 >
111 <Link to="/settings/recipes" className="button">
112 {intl.formatMessage(messages.getStarted)}
113 </Link>
114 </Appear>
115 </div>
116 </Appear>
117 </>
74 )} 118 )}
75 {services.map(service => ( 119 {services.map(service => (
76 <ServiceView 120 <ServiceView
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
new file mode 100644
index 000000000..a63f5f7b5
--- /dev/null
+++ b/src/components/ui/FeatureItem.js
@@ -0,0 +1,36 @@
1import React from 'react';
2import injectSheet from 'react-jss';
3import { Icon } from '@meetfranz/ui';
4import classnames from 'classnames';
5
6const styles = theme => ({
7 featureItem: {
8 borderBottom: [1, 'solid', theme.legacyStyles.themeGrayDark],
9 padding: [8, 0],
10 display: 'flex',
11 alignItems: 'center',
12 },
13 featureIcon: {
14 fill: theme.brandSuccess,
15 marginRight: 10,
16 },
17});
18
19export const FeatureItem = injectSheet(styles)(({
20 classes, className, name, icon,
21}) => (
22 <li className={classnames({
23 [classes.featureItem]: true,
24 [className]: className,
25 })}
26 >
27 {icon ? (
28 <span className={classes.featureIcon}>{icon}</span>
29 ) : (
30 <Icon icon="mdiCheckCircle" className={classes.featureIcon} size={1.5} />
31 )}
32 {name}
33 </li>
34));
35
36export default FeatureItem;
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
new file mode 100644
index 000000000..62944ad75
--- /dev/null
+++ b/src/components/ui/FeatureList.js
@@ -0,0 +1,89 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4
5import { FeatureItem } from './FeatureItem';
6
7const messages = defineMessages({
8 unlimitedServices: {
9 id: 'pricing.features.unlimitedServices',
10 defaultMessage: '!!!Add unlimited services',
11 },
12 spellchecker: {
13 id: 'pricing.features.spellchecker',
14 defaultMessage: '!!!Spellchecker support',
15 },
16 workspaces: {
17 id: 'pricing.features.workspaces',
18 defaultMessage: '!!!Workspaces',
19 },
20 customWebsites: {
21 id: 'pricing.features.customWebsites',
22 defaultMessage: '!!!Add Custom Websites',
23 },
24 onPremise: {
25 id: 'pricing.features.onPremise',
26 defaultMessage: '!!!On-premise & other Hosted Services',
27 },
28 thirdPartyServices: {
29 id: 'pricing.features.thirdPartyServices',
30 defaultMessage: '!!!Install 3rd party services',
31 },
32 serviceProxies: {
33 id: 'pricing.features.serviceProxies',
34 defaultMessage: '!!!Service Proxies',
35 },
36 teamManagement: {
37 id: 'pricing.features.teamManagement',
38 defaultMessage: '!!!Team Management',
39 },
40 appDelays: {
41 id: 'pricing.features.appDelays',
42 defaultMessage: '!!!No Waiting Screens',
43 },
44 adFree: {
45 id: 'pricing.features.adFree',
46 defaultMessage: '!!!Forever ad-free',
47 },
48});
49
50export class FeatureList extends Component {
51 static propTypes = {
52 className: PropTypes.string,
53 featureClassName: PropTypes.string,
54 };
55
56 static defaultProps = {
57 className: '',
58 featureClassName: '',
59 }
60
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 render() {
66 const {
67 className,
68 featureClassName,
69 } = this.props;
70 const { intl } = this.context;
71
72 return (
73 <ul className={className}>
74 <FeatureItem name={intl.formatMessage(messages.unlimitedServices)} className={featureClassName} />
75 <FeatureItem name={intl.formatMessage(messages.spellchecker)} className={featureClassName} />
76 <FeatureItem name={intl.formatMessage(messages.workspaces)} className={featureClassName} />
77 <FeatureItem name={intl.formatMessage(messages.customWebsites)} className={featureClassName} />
78 <FeatureItem name={intl.formatMessage(messages.onPremise)} className={featureClassName} />
79 <FeatureItem name={intl.formatMessage(messages.thirdPartyServices)} className={featureClassName} />
80 <FeatureItem name={intl.formatMessage(messages.serviceProxies)} className={featureClassName} />
81 <FeatureItem name={intl.formatMessage(messages.teamManagement)} className={featureClassName} />
82 <FeatureItem name={intl.formatMessage(messages.appDelays)} className={featureClassName} />
83 <FeatureItem name={intl.formatMessage(messages.adFree)} className={featureClassName} />
84 </ul>
85 );
86 }
87}
88
89export default FeatureList;