diff options
author | Stefan Malzner <stefan@adlk.io> | 2019-08-08 19:50:17 +0200 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2019-08-08 19:50:17 +0200 |
commit | 524d55f46e3834a84db17945eaa1c65891f06547 (patch) | |
tree | e7b1e5dcd77d1b094fd5bc911e75bf40c47862cc /src/components | |
parent | Fix service restriction on pro plan (diff) | |
download | ferdium-app-524d55f46e3834a84db17945eaa1c65891f06547.tar.gz ferdium-app-524d55f46e3834a84db17945eaa1c65891f06547.tar.zst ferdium-app-524d55f46e3834a84db17945eaa1c65891f06547.zip |
Add option to subscribe to trial via account dashboard
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/auth/Pricing.js | 2 | ||||
-rw-r--r-- | src/components/settings/account/AccountDashboard.js | 114 | ||||
-rw-r--r-- | src/components/subscription/SubscriptionForm.js | 238 |
3 files changed, 134 insertions, 220 deletions
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js index d20779025..cbeaaa5d9 100644 --- a/src/components/auth/Pricing.js +++ b/src/components/auth/Pricing.js | |||
@@ -8,7 +8,7 @@ import classnames from 'classnames'; | |||
8 | 8 | ||
9 | import { Button } from '@meetfranz/forms'; | 9 | import { Button } from '@meetfranz/forms'; |
10 | import { FeatureItem } from '../ui/FeatureItem'; | 10 | import { FeatureItem } from '../ui/FeatureItem'; |
11 | import FeatureList from '../ui/FeatureList'; | 11 | import { FeatureList } from '../ui/FeatureList'; |
12 | 12 | ||
13 | 13 | ||
14 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js index 0366dc1ee..079d50380 100644 --- a/src/components/settings/account/AccountDashboard.js +++ b/src/components/settings/account/AccountDashboard.js | |||
@@ -1,14 +1,18 @@ | |||
1 | import React, { Component, Fragment } 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, PropTypes as MobxPropTypes } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import ReactTooltip from 'react-tooltip'; | 5 | import ReactTooltip from 'react-tooltip'; |
6 | import { ProBadge } from '@meetfranz/ui'; | 6 | import { |
7 | ProBadge, H2, H1, H3, | ||
8 | } from '@meetfranz/ui'; | ||
9 | import moment from 'moment'; | ||
7 | 10 | ||
8 | import Loader from '../../ui/Loader'; | 11 | import Loader from '../../ui/Loader'; |
9 | import Button from '../../ui/Button'; | 12 | import Button from '../../ui/Button'; |
10 | import Infobox from '../../ui/Infobox'; | 13 | import Infobox from '../../ui/Infobox'; |
11 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; | 14 | import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; |
15 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
12 | 16 | ||
13 | const messages = defineMessages({ | 17 | const messages = defineMessages({ |
14 | headline: { | 18 | headline: { |
@@ -20,8 +24,8 @@ const messages = defineMessages({ | |||
20 | defaultMessage: '!!!Your Subscription', | 24 | defaultMessage: '!!!Your Subscription', |
21 | }, | 25 | }, |
22 | headlineUpgrade: { | 26 | headlineUpgrade: { |
23 | id: 'settings.account.headlineUpgrade', | 27 | id: 'settings.account.headlineTrialUpgrade', |
24 | defaultMessage: '!!!Upgrade your Account', | 28 | defaultMessage: '!!!Get the free 14 day Franz Professional Trial', |
25 | }, | 29 | }, |
26 | headlineDangerZone: { | 30 | headlineDangerZone: { |
27 | id: 'settings.account.headlineDangerZone', | 31 | id: 'settings.account.headlineDangerZone', |
@@ -71,6 +75,22 @@ const messages = defineMessages({ | |||
71 | id: 'settings.account.deleteEmailSent', | 75 | id: 'settings.account.deleteEmailSent', |
72 | defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', | 76 | defaultMessage: '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', |
73 | }, | 77 | }, |
78 | trial: { | ||
79 | id: 'settings.account.trial', | ||
80 | defaultMessage: '!!!Free Trial', | ||
81 | }, | ||
82 | yourLicense: { | ||
83 | id: 'settings.account.yourLicense', | ||
84 | defaultMessage: '!!!Your license:', | ||
85 | }, | ||
86 | trialEndsIn: { | ||
87 | id: 'settings.account.trialEndsIn', | ||
88 | defaultMessage: '!!!Your free trial ends in {duration}.', | ||
89 | }, | ||
90 | trialUpdateBillingInformation: { | ||
91 | id: 'settings.account.trialUpdateBillingInfo', | ||
92 | defaultMessage: '!!!Please update your billing info to continue using {license} after your trial period.', | ||
93 | }, | ||
74 | }); | 94 | }); |
75 | 95 | ||
76 | export default @observer class AccountDashboard extends Component { | 96 | export default @observer class AccountDashboard extends Component { |
@@ -110,6 +130,13 @@ export default @observer class AccountDashboard extends Component { | |||
110 | } = this.props; | 130 | } = this.props; |
111 | const { intl } = this.context; | 131 | const { intl } = this.context; |
112 | 132 | ||
133 | let planName = ''; | ||
134 | |||
135 | if (user.team && user.team.plan) { | ||
136 | planName = i18nPlanName(user.team.plan, intl); | ||
137 | console.log(planName); | ||
138 | } | ||
139 | |||
113 | return ( | 140 | return ( |
114 | <div className="settings__main"> | 141 | <div className="settings__main"> |
115 | <div className="settings__header"> | 142 | <div className="settings__header"> |
@@ -147,7 +174,7 @@ export default @observer class AccountDashboard extends Component { | |||
147 | /> | 174 | /> |
148 | </div> | 175 | </div> |
149 | <div className="account__info"> | 176 | <div className="account__info"> |
150 | <h2> | 177 | <H1> |
151 | <span className="username">{`${user.firstname} ${user.lastname}`}</span> | 178 | <span className="username">{`${user.firstname} ${user.lastname}`}</span> |
152 | {user.isPremium && ( | 179 | {user.isPremium && ( |
153 | <> | 180 | <> |
@@ -156,7 +183,7 @@ export default @observer class AccountDashboard extends Component { | |||
156 | {/* <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> */} | 183 | {/* <span className="badge badge--premium">{intl.formatMessage(messages.accountTypePremium)}</span> */} |
157 | </> | 184 | </> |
158 | )} | 185 | )} |
159 | </h2> | 186 | </H1> |
160 | <p> | 187 | <p> |
161 | {user.organization && `${user.organization}, `} | 188 | {user.organization && `${user.organization}, `} |
162 | {user.email} | 189 | {user.email} |
@@ -197,54 +224,61 @@ export default @observer class AccountDashboard extends Component { | |||
197 | {user.isSubscriptionOwner && ( | 224 | {user.isSubscriptionOwner && ( |
198 | <div className="account"> | 225 | <div className="account"> |
199 | <div className="account__box"> | 226 | <div className="account__box"> |
200 | <h2> | 227 | <H2> |
201 | Your license: {user.team.plan} | 228 | {intl.formatMessage(messages.yourLicense)} |
202 | </h2> | 229 | </H2> |
230 | <H3> | ||
231 | {planName} | ||
232 | {user.team.isTrial && ( | ||
233 | <> | ||
234 | {' – '} | ||
235 | {intl.formatMessage(messages.trial)} | ||
236 | </> | ||
237 | )} | ||
238 | </H3> | ||
203 | {user.team.isTrial && ( | 239 | {user.team.isTrial && ( |
204 | <> | 240 | <> |
205 | <p> | 241 | <p> |
206 | Trial ends in 14 days | 242 | {intl.formatMessage(messages.trialEndsIn, { |
243 | duration: moment.duration(moment().diff(user.team.trialEnd)).humanize(), | ||
244 | })} | ||
245 | </p> | ||
246 | <p> | ||
247 | {intl.formatMessage(messages.trialUpdateBillingInformation, { | ||
248 | license: planName, | ||
249 | })} | ||
207 | </p> | 250 | </p> |
208 | </> | 251 | </> |
209 | )} | 252 | )} |
210 | {user.isPremium && ( | 253 | <div className="manage-user-links"> |
211 | <div className="manage-user-links"> | 254 | <Button |
212 | <Button | 255 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} |
213 | label={intl.formatMessage(messages.manageSubscriptionButtonLabel)} | 256 | className="franz-form__button--inverted" |
214 | className="franz-form__button--inverted" | 257 | onClick={openBilling} |
215 | onClick={openBilling} | 258 | /> |
216 | /> | 259 | <Button |
217 | <Button | 260 | label={intl.formatMessage(messages.invoicesButton)} |
218 | label={intl.formatMessage(messages.invoicesButton)} | 261 | className="franz-form__button--inverted" |
219 | className="franz-form__button--inverted" | 262 | onClick={openInvoices} |
220 | onClick={openInvoices} | 263 | /> |
221 | /> | 264 | </div> |
222 | </div> | ||
223 | )} | ||
224 | </div> | 265 | </div> |
225 | </div> | 266 | </div> |
226 | )} | 267 | )} |
227 | </> | 268 | {!user.isPremium && ( |
228 | )} | 269 | <div className="account franz-form"> |
229 | 270 | <div className="account__box"> | |
230 | {!user.isPremium && ( | 271 | <H2>{intl.formatMessage(messages.headlineUpgrade)}</H2> |
231 | isLoadingPlans ? ( | 272 | <SubscriptionForm /> |
232 | <Loader /> | 273 | </div> |
233 | ) : ( | ||
234 | <div className="account franz-form"> | ||
235 | <div className="account__box"> | ||
236 | <h2>{intl.formatMessage(messages.headlineUpgrade)}</h2> | ||
237 | <SubscriptionForm | ||
238 | onCloseWindow={onCloseSubscriptionWindow} | ||
239 | /> | ||
240 | </div> | 274 | </div> |
241 | </div> | 275 | )} |
242 | ) | 276 | </> |
243 | )} | 277 | )} |
244 | 278 | ||
245 | <div className="account franz-form"> | 279 | <div className="account franz-form"> |
246 | <div className="account__box"> | 280 | <div className="account__box"> |
247 | <h2>{intl.formatMessage(messages.headlineDangerZone)}</h2> | 281 | <H2>{intl.formatMessage(messages.headlineDangerZone)}</H2> |
248 | {!isDeleteAccountSuccessful && ( | 282 | {!isDeleteAccountSuccessful && ( |
249 | <div className="account__subscription"> | 283 | <div className="account__subscription"> |
250 | <p>{intl.formatMessage(messages.deleteInfo)}</p> | 284 | <p>{intl.formatMessage(messages.deleteInfo)}</p> |
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js index 50f1e0522..0c630f047 100644 --- a/src/components/subscription/SubscriptionForm.js +++ b/src/components/subscription/SubscriptionForm.js | |||
@@ -1,214 +1,94 @@ | |||
1 | import React, { Component, Fragment } 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 injectSheet from 'react-jss'; | ||
5 | 6 | ||
6 | import Form from '../../lib/Form'; | 7 | import { H3 } from '@meetfranz/ui'; |
7 | import Radio from '../ui/Radio'; | ||
8 | import Button from '../ui/Button'; | ||
9 | import Loader from '../ui/Loader'; | ||
10 | 8 | ||
11 | import { required } from '../../helpers/validation-helpers'; | 9 | import { Button } from '@meetfranz/forms'; |
10 | import { FeatureList } from '../ui/FeatureList'; | ||
11 | import { FeatureItem } from '../ui/FeatureItem'; | ||
12 | 12 | ||
13 | const messages = defineMessages({ | 13 | const messages = defineMessages({ |
14 | submitButtonLabel: { | 14 | submitButtonLabel: { |
15 | id: 'subscription.submit.label', | 15 | id: 'subscription.cta.activateTrial', |
16 | defaultMessage: '!!!Support the development of Franz', | 16 | defaultMessage: '!!!Yes, upgrade my account to Franz Professional', |
17 | }, | ||
18 | paymentSessionError: { | ||
19 | id: 'subscription.paymentSessionError', | ||
20 | defaultMessage: '!!!Could not initialize payment form', | ||
21 | }, | ||
22 | typeFree: { | ||
23 | id: 'subscription.type.free', | ||
24 | defaultMessage: '!!!free', | ||
25 | }, | ||
26 | typeMonthly: { | ||
27 | id: 'subscription.type.month', | ||
28 | defaultMessage: '!!!month', | ||
29 | }, | ||
30 | typeYearly: { | ||
31 | id: 'subscription.type.year', | ||
32 | defaultMessage: '!!!year', | ||
33 | }, | 17 | }, |
34 | includedFeatures: { | 18 | includedFeatures: { |
35 | id: 'subscription.includedFeatures', | 19 | id: 'subscription.includedProFeatures', |
36 | defaultMessage: '!!!The Franz Premium Supporter Account includes', | 20 | defaultMessage: '!!!The Franz Professional Plan includes:', |
37 | }, | ||
38 | onpremise: { | ||
39 | id: 'subscription.features.onpremise.mattermost', | ||
40 | defaultMessage: '!!!Add on-premise/hosted services like Mattermost', | ||
41 | }, | ||
42 | noInterruptions: { | ||
43 | id: 'subscription.features.noInterruptions', | ||
44 | defaultMessage: '!!!No app delays & nagging to upgrade license', | ||
45 | }, | 21 | }, |
46 | proxy: { | 22 | noStringsAttachedHeadline: { |
47 | id: 'subscription.features.proxy', | 23 | id: 'pricing.trial.terms.headline', |
48 | defaultMessage: '!!!Proxy support for services', | 24 | defaultMessage: '!!!No strings attached', |
49 | }, | 25 | }, |
50 | spellchecker: { | 26 | noCreditCard: { |
51 | id: 'subscription.features.spellchecker', | 27 | id: 'pricing.trial.terms.noCreditCard', |
52 | defaultMessage: '!!!Support for Spellchecker', | 28 | defaultMessage: '!!!No credit card required', |
53 | }, | 29 | }, |
54 | workspaces: { | 30 | automaticTrialEnd: { |
55 | id: 'subscription.features.workspaces', | 31 | id: 'pricing.trial.terms.automaticTrialEnd', |
56 | defaultMessage: '!!!Organize your services in workspaces', | 32 | defaultMessage: '!!!Your free trial ends automatically after 14 days', |
57 | }, | 33 | }, |
58 | ads: { | 34 | }); |
59 | id: 'subscription.features.ads', | 35 | |
60 | defaultMessage: '!!!No ads, ever!', | 36 | const styles = () => ({ |
61 | }, | 37 | activateTrialButton: { |
62 | comingSoon: { | 38 | margin: [40, 0, 50], |
63 | id: 'subscription.features.comingSoon', | ||
64 | defaultMessage: '!!!coming soon', | ||
65 | }, | 39 | }, |
66 | euTaxInfo: { | 40 | keyTerms: { |
67 | id: 'subscription.euTaxInfo', | 41 | marginTop: 20, |
68 | defaultMessage: '!!!EU residents: local sales tax may apply', | ||
69 | }, | 42 | }, |
70 | }); | 43 | }); |
71 | 44 | ||
72 | export default @observer class SubscriptionForm extends Component { | 45 | export default @observer @injectSheet(styles) class SubscriptionForm extends Component { |
73 | static propTypes = { | 46 | static propTypes = { |
74 | plan: MobxPropTypes.objectOrObservableObject.isRequired, | 47 | activateTrial: PropTypes.func.isRequired, |
75 | isLoading: PropTypes.bool.isRequired, | 48 | isActivatingTrial: PropTypes.bool.isRequired, |
76 | handlePayment: PropTypes.func.isRequired, | 49 | classes: PropTypes.object.isRequired, |
77 | retryPlanRequest: PropTypes.func.isRequired, | ||
78 | isCreatingHostedPage: PropTypes.bool.isRequired, | ||
79 | error: PropTypes.bool.isRequired, | ||
80 | showSkipOption: PropTypes.bool, | ||
81 | skipAction: PropTypes.func, | ||
82 | skipButtonLabel: PropTypes.string, | ||
83 | hideInfo: PropTypes.bool.isRequired, | ||
84 | }; | ||
85 | |||
86 | static defaultProps = { | ||
87 | showSkipOption: false, | ||
88 | skipAction: () => null, | ||
89 | skipButtonLabel: '', | ||
90 | }; | 50 | }; |
91 | 51 | ||
92 | static contextTypes = { | 52 | static contextTypes = { |
93 | intl: intlShape, | 53 | intl: intlShape, |
94 | }; | 54 | }; |
95 | 55 | ||
96 | componentWillMount() { | ||
97 | this.form = this.prepareForm(); | ||
98 | } | ||
99 | |||
100 | prepareForm() { | ||
101 | const { intl } = this.context; | ||
102 | |||
103 | const form = { | ||
104 | fields: { | ||
105 | paymentTier: { | ||
106 | value: 'year', | ||
107 | validators: [required], | ||
108 | options: [{ | ||
109 | value: 'month', | ||
110 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'month') | ||
111 | ? `${this.props.plan.month.price} / ${intl.formatMessage(messages.typeMonthly)}` | ||
112 | : 'monthly'}`, | ||
113 | }, { | ||
114 | value: 'year', | ||
115 | label: `€ ${Object.hasOwnProperty.call(this.props.plan, 'year') | ||
116 | ? `${this.props.plan.year.price} / ${intl.formatMessage(messages.typeYearly)}` | ||
117 | : 'yearly'}`, | ||
118 | }], | ||
119 | }, | ||
120 | }, | ||
121 | }; | ||
122 | |||
123 | if (this.props.showSkipOption) { | ||
124 | form.fields.paymentTier.options.unshift({ | ||
125 | value: 'skip', | ||
126 | label: `€ 0 / ${intl.formatMessage(messages.typeFree)}`, | ||
127 | }); | ||
128 | } | ||
129 | |||
130 | return new Form(form, this.context.intl); | ||
131 | } | ||
132 | |||
133 | render() { | 56 | render() { |
134 | const { | 57 | const { |
135 | isLoading, | 58 | isActivatingTrial, |
136 | isCreatingHostedPage, | 59 | activateTrial, |
137 | handlePayment, | 60 | classes, |
138 | retryPlanRequest, | ||
139 | error, | ||
140 | showSkipOption, | ||
141 | skipAction, | ||
142 | skipButtonLabel, | ||
143 | hideInfo, | ||
144 | } = this.props; | 61 | } = this.props; |
145 | const { intl } = this.context; | 62 | const { intl } = this.context; |
146 | 63 | ||
147 | if (error) { | 64 | console.log('isActivatingTrial', isActivatingTrial); |
148 | return ( | ||
149 | <Button | ||
150 | label="Reload" | ||
151 | onClick={retryPlanRequest} | ||
152 | isLoaded={!isLoading} | ||
153 | /> | ||
154 | ); | ||
155 | } | ||
156 | 65 | ||
157 | return ( | 66 | return ( |
158 | <Loader loaded={!isLoading}> | 67 | <> |
159 | <Radio field={this.form.$('paymentTier')} showLabel={false} className="paymentTiers" /> | 68 | <H3 className={classes.keyTerms}> |
160 | {!hideInfo && ( | 69 | {intl.formatMessage(messages.noStringsAttachedHeadline)} |
161 | <div className="subscription__premium-info"> | 70 | </H3> |
162 | <p> | 71 | <ul> |
163 | <strong>{intl.formatMessage(messages.includedFeatures)}</strong> | 72 | <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} /> |
164 | </p> | 73 | <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} /> |
165 | <div className="subscription"> | 74 | </ul> |
166 | <ul className="subscription__premium-features"> | 75 | |
167 | <li>{intl.formatMessage(messages.onpremise)}</li> | 76 | <Button |
168 | <li> | 77 | label={intl.formatMessage(messages.submitButtonLabel)} |
169 | {intl.formatMessage(messages.noInterruptions)} | 78 | className={classes.activateTrialButton} |
170 | </li> | 79 | busy={isActivatingTrial} |
171 | <li> | 80 | onClick={activateTrial} |
172 | {intl.formatMessage(messages.spellchecker)} | 81 | stretch |
173 | </li> | 82 | /> |
174 | <li> | 83 | <div className="subscription__premium-info"> |
175 | {intl.formatMessage(messages.proxy)} | 84 | <H3> |
176 | </li> | 85 | {intl.formatMessage(messages.includedFeatures)} |
177 | <li> | 86 | </H3> |
178 | {intl.formatMessage(messages.workspaces)} | 87 | <div className="subscription"> |
179 | </li> | 88 | <FeatureList /> |
180 | <li> | ||
181 | {intl.formatMessage(messages.ads)} | ||
182 | </li> | ||
183 | </ul> | ||
184 | </div> | ||
185 | </div> | 89 | </div> |
186 | )} | 90 | </div> |
187 | <Fragment> | 91 | </> |
188 | {error.code === 'no-payment-session' && ( | ||
189 | <p className="error-message center">{intl.formatMessage(messages.paymentSessionError)}</p> | ||
190 | )} | ||
191 | </Fragment> | ||
192 | {showSkipOption && this.form.$('paymentTier').value === 'skip' ? ( | ||
193 | <Button | ||
194 | label={skipButtonLabel} | ||
195 | className="auth__button" | ||
196 | onClick={skipAction} | ||
197 | /> | ||
198 | ) : ( | ||
199 | <Button | ||
200 | label={intl.formatMessage(messages.submitButtonLabel)} | ||
201 | className="auth__button" | ||
202 | loaded={!isCreatingHostedPage} | ||
203 | onClick={() => handlePayment(this.form.$('paymentTier').value)} | ||
204 | /> | ||
205 | )} | ||
206 | {this.form.$('paymentTier').value !== 'skip' && ( | ||
207 | <p className="legal"> | ||
208 | {intl.formatMessage(messages.euTaxInfo)} | ||
209 | </p> | ||
210 | )} | ||
211 | </Loader> | ||
212 | ); | 92 | ); |
213 | } | 93 | } |
214 | } | 94 | } |