diff options
author | 2019-07-04 15:54:27 +0200 | |
---|---|---|
committer | 2019-07-04 15:54:27 +0200 | |
commit | 268db27162e8d2cd0252b1be9bf69006cf6323ca (patch) | |
tree | 6ca47ee4159ba79f88d8848ac2ba9a8a8a7700da /src/components | |
parent | Merge branch 'release/5.2.0-beta.4' into feature/new-pricing (diff) | |
download | ferdium-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.js | 246 | ||||
-rw-r--r-- | src/components/services/content/Services.js | 80 | ||||
-rw-r--r-- | src/components/ui/FeatureItem.js | 36 | ||||
-rw-r--r-- | src/components/ui/FeatureList.js | 89 |
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 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | // import { Link } from 'react-router'; | 5 | import injectSheet from 'react-jss'; |
6 | import { H2, Loader } from '@meetfranz/ui'; | ||
7 | import classnames from 'classnames'; | ||
8 | |||
9 | import { Button } from '@meetfranz/forms'; | ||
10 | import { FeatureItem } from '../ui/FeatureItem'; | ||
11 | import FeatureList from '../ui/FeatureList'; | ||
6 | 12 | ||
7 | // import Button from '../ui/Button'; | ||
8 | import Loader from '../ui/Loader'; | ||
9 | import Appear from '../ui/effects/Appear'; | ||
10 | import SubscriptionForm from '../../containers/subscription/SubscriptionFormScreen'; | ||
11 | 13 | ||
12 | const messages = defineMessages({ | 14 | const 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 | ||
31 | export default @observer class Signup extends Component { | 53 | const 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 | |||
98 | export 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'; | |||
3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; | 3 | import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { Link } from 'react-router'; | 4 | import { Link } from 'react-router'; |
5 | import { defineMessages, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import Confetti from 'react-confetti'; | ||
7 | import ms from 'ms'; | ||
8 | import injectSheet from 'react-jss'; | ||
6 | 9 | ||
7 | import ServiceView from './ServiceView'; | 10 | import ServiceView from './ServiceView'; |
8 | import Appear from '../../ui/effects/Appear'; | 11 | import Appear from '../../ui/effects/Appear'; |
@@ -18,7 +21,16 @@ const messages = defineMessages({ | |||
18 | }, | 21 | }, |
19 | }); | 22 | }); |
20 | 23 | ||
21 | export default @observer class Services extends Component { | 24 | |
25 | const styles = { | ||
26 | confettiContainer: { | ||
27 | position: 'absolute', | ||
28 | width: '100%', | ||
29 | zIndex: 0, | ||
30 | }, | ||
31 | }; | ||
32 | |||
33 | export 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 @@ | |||
1 | import React from 'react'; | ||
2 | import injectSheet from 'react-jss'; | ||
3 | import { Icon } from '@meetfranz/ui'; | ||
4 | import classnames from 'classnames'; | ||
5 | |||
6 | const 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 | |||
19 | export 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 | |||
36 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { defineMessages, intlShape } from 'react-intl'; | ||
4 | |||
5 | import { FeatureItem } from './FeatureItem'; | ||
6 | |||
7 | const 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 | |||
50 | export 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 | |||
89 | export default FeatureList; | ||