aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/auth/Pricing.js2
-rw-r--r--src/components/settings/account/AccountDashboard.js114
-rw-r--r--src/components/subscription/SubscriptionForm.js238
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
9import { Button } from '@meetfranz/forms'; 9import { Button } from '@meetfranz/forms';
10import { FeatureItem } from '../ui/FeatureItem'; 10import { FeatureItem } from '../ui/FeatureItem';
11import FeatureList from '../ui/FeatureList'; 11import { FeatureList } from '../ui/FeatureList';
12 12
13 13
14const messages = defineMessages({ 14const 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 @@
1import React, { Component, Fragment } 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, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { ProBadge } from '@meetfranz/ui'; 6import {
7 ProBadge, H2, H1, H3,
8} from '@meetfranz/ui';
9import moment from 'moment';
7 10
8import Loader from '../../ui/Loader'; 11import Loader from '../../ui/Loader';
9import Button from '../../ui/Button'; 12import Button from '../../ui/Button';
10import Infobox from '../../ui/Infobox'; 13import Infobox from '../../ui/Infobox';
11import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen'; 14import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
15import { i18nPlanName } from '../../../helpers/plan-helpers';
12 16
13const messages = defineMessages({ 17const 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
76export default @observer class AccountDashboard extends Component { 96export 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 @@
1import React, { Component, Fragment } 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';
5import injectSheet from 'react-jss';
5 6
6import Form from '../../lib/Form'; 7import { H3 } from '@meetfranz/ui';
7import Radio from '../ui/Radio';
8import Button from '../ui/Button';
9import Loader from '../ui/Loader';
10 8
11import { required } from '../../helpers/validation-helpers'; 9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
11import { FeatureItem } from '../ui/FeatureItem';
12 12
13const messages = defineMessages({ 13const 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!', 36const 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
72export default @observer class SubscriptionForm extends Component { 45export 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}