aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/planSelection/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/planSelection/components')
-rw-r--r--src/features/planSelection/components/PlanItem.js186
-rw-r--r--src/features/planSelection/components/PlanSelection.js233
2 files changed, 419 insertions, 0 deletions
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js
new file mode 100644
index 000000000..a49cd40d3
--- /dev/null
+++ b/src/features/planSelection/components/PlanItem.js
@@ -0,0 +1,186 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7import color from 'color';
8
9import { H2 } from '@meetfranz/ui';
10
11import { Button } from '@meetfranz/forms';
12import { mdiArrowRight } from '@mdi/js';
13// import { FeatureList } from '../ui/FeatureList';
14// import { PLANS, PAYMENT_INTERVAL } from '../../config';
15// import { i18nPlanName, i18nIntervalName } from '../../helpers/plan-helpers';
16// import { PLAN_INTERVAL_CONFIG_TYPE } from './types';
17
18const messages = defineMessages({
19 perMonth: {
20 id: 'subscription.interval.perMonth',
21 defaultMessage: '!!!per month',
22 },
23 perMonthPerUser: {
24 id: 'subscription.interval.perMonthPerUser',
25 defaultMessage: '!!!per month & user',
26 },
27});
28
29const styles = theme => ({
30 root: {
31 display: 'flex',
32 flexDirection: 'column',
33 borderRadius: theme.borderRadius,
34 flex: 1,
35 color: theme.styleTypes.primary.accent,
36 overflow: 'hidden',
37 textAlign: 'center',
38
39 '& h2': {
40 textAlign: 'center',
41 marginBottom: 20,
42 fontSize: 30,
43 color: theme.styleTypes.primary.contrast,
44 // fontWeight: 'bold',
45 },
46 },
47 currency: {
48 fontSize: 35,
49 },
50 priceWrapper: {
51 height: 50,
52 },
53 price: {
54 fontSize: 50,
55
56 '& sup': {
57 fontSize: 20,
58 verticalAlign: 20,
59 },
60 },
61 interval: {
62 // paddingBottom: 40,
63 },
64 text: {
65 marginBottom: 'auto',
66 },
67 cta: {
68 background: theme.styleTypes.primary.accent,
69 color: theme.styleTypes.primary.contrast,
70 margin: [40, 'auto', 0, 'auto'],
71
72 // '&:active': {
73 // opacity: 0.7,
74 // },
75 },
76 divider: {
77 width: 40,
78 border: 0,
79 borderTop: [1, 'solid', theme.styleTypes.primary.contrast],
80 margin: [30, 'auto'],
81 },
82 header: {
83 padding: 20,
84 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
85 color: theme.styleTypes.primary.contrast,
86 },
87 content: {
88 padding: 20,
89 // border: [1, 'solid', 'red'],
90 background: '#EFEFEF',
91 },
92 simpleCTA: {
93 background: 'none',
94 color: theme.styleTypes.primary.accent,
95
96 '& svg': {
97 fill: theme.styleTypes.primary.accent,
98 },
99 },
100});
101
102
103export default @observer @injectSheet(styles) class PlanItem extends Component {
104 static propTypes = {
105 name: PropTypes.string.isRequired,
106 text: PropTypes.string.isRequired,
107 price: PropTypes.number.isRequired,
108 currency: PropTypes.string.isRequired,
109 upgrade: PropTypes.func.isRequired,
110 ctaLabel: PropTypes.string.isRequired,
111 simpleCTA: PropTypes.bool,
112 perUser: PropTypes.bool,
113 classes: PropTypes.object.isRequired,
114 children: PropTypes.element,
115 };
116
117 static defaultProps = {
118 simpleCTA: false,
119 perUser: false,
120 children: null,
121 }
122
123 static contextTypes = {
124 intl: intlShape,
125 };
126
127 render() {
128 const {
129 name,
130 text,
131 price,
132 currency,
133 classes,
134 upgrade,
135 ctaLabel,
136 simpleCTA,
137 perUser,
138 children,
139 } = this.props;
140 const { intl } = this.context;
141
142 const priceParts = `${price}`.split('.');
143 // const intervalName = i18nIntervalName(PAYMENT_INTERVAL.MONTHLY, intl);
144
145 return (
146 <div className={classes.root}>
147 <div className={classes.header}>
148 <H2 className={classes.planName}>{name}</H2>
149 <p className={classes.text}>
150 {text}
151 </p>
152 <hr className={classes.divider} />
153 <p className={classes.priceWrapper}>
154 <span className={classes.currency}>{currency}</span>
155 <span className={classes.price}>
156 {priceParts[0]}
157 <sup>{priceParts[1]}</sup>
158 </span>
159 </p>
160 <p className={classes.interval}>
161 {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)}
162 </p>
163 </div>
164
165 <div className={classes.content}>
166 {children}
167
168 <Button
169 className={classnames({
170 [classes.cta]: true,
171 [classes.simpleCTA]: simpleCTA,
172 })}
173 icon={simpleCTA ? mdiArrowRight : null}
174 label={(
175 <>
176 {ctaLabel}
177 </>
178 )}
179 onClick={upgrade}
180 />
181 </div>
182
183 </div>
184 );
185 }
186}
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
new file mode 100644
index 000000000..84d2d9e89
--- /dev/null
+++ b/src/features/planSelection/components/PlanSelection.js
@@ -0,0 +1,233 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl';
6import { H1, H2, Icon } from '@meetfranz/ui';
7import color from 'color';
8
9import { mdiRocket } from '@mdi/js';
10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { PLANS } from '../../../config';
13import { FeatureList } from '../../../components/ui/FeatureList';
14
15const messages = defineMessages({
16 welcome: {
17 id: 'feature.planSelection.fullscreen.welcome',
18 defaultMessage: '!!!Welcome back, {name}',
19 },
20 subheadline: {
21 id: 'feature.planSelection.fullscreen.subheadline',
22 defaultMessage: '!!!It\'s time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.',
23 },
24 textFree: {
25 id: 'feature.planSelection.free.text',
26 defaultMessage: '!!!Basic functionality',
27 },
28 textPersonal: {
29 id: 'feature.planSelection.personal.text',
30 defaultMessage: '!!!More services, no waiting - ideal for personal use.',
31 },
32 textProfessional: {
33 id: 'feature.planSelection.pro.text',
34 defaultMessage: '!!!Unlimited services and professional features for you - and your team.',
35 },
36 ctaStayOnFree: {
37 id: 'feature.planSelection.cta.stayOnFree',
38 defaultMessage: '!!!Stay on Free',
39 },
40 ctaDowngradeFree: {
41 id: 'feature.planSelection.cta.ctaDowngradeFree',
42 defaultMessage: '!!!Downgrade to Free',
43 },
44 actionTrial: {
45 id: 'feature.planSelection.cta.trial',
46 defaultMessage: '!!!Start my free 14-days Trial',
47 },
48 shortActionPersonal: {
49 id: 'feature.planSelection.cta.upgradePersonal',
50 defaultMessage: '!!!Choose Personal',
51 },
52 shortActionPro: {
53 id: 'feature.planSelection.cta.upgradePro',
54 defaultMessage: '!!!Choose Professional',
55 },
56 fullFeatureList: {
57 id: 'feature.planSelection.fullFeatureList',
58 defaultMessage: '!!!Complete comparison of all plans',
59 },
60});
61
62const styles = theme => ({
63 root: {
64 background: theme.colorModalOverlayBackground,
65 width: '100%',
66 height: '100%',
67 position: 'absolute',
68 top: 0,
69 left: 0,
70 display: 'flex',
71 justifyContent: 'center',
72 alignItems: 'center',
73 zIndex: 999999,
74 },
75 container: {
76 width: '80%',
77 height: 'auto',
78 background: theme.styleTypes.primary.accent,
79 padding: 40,
80 borderRadius: theme.borderRadius,
81 maxWidth: 1000,
82
83 '& h1, & h2': {
84 textAlign: 'center',
85 },
86 },
87 plans: {
88 display: 'flex',
89 margin: [40, 0, 0],
90 height: 'auto',
91
92 '& > div': {
93 margin: [0, 15],
94 flex: 1,
95 height: 'auto',
96 background: theme.styleTypes.primary.contrast,
97 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
98 },
99 },
100 bigIcon: {
101 background: theme.styleTypes.danger.accent,
102 width: 120,
103 height: 120,
104 display: 'flex',
105 alignItems: 'center',
106 borderRadius: '100%',
107 justifyContent: 'center',
108 margin: [-100, 'auto', 20],
109
110 '& svg': {
111 width: '80px !important',
112 },
113 },
114 headline: {
115 fontSize: 40,
116 },
117 subheadline: {
118 maxWidth: 660,
119 fontSize: 22,
120 lineHeight: 1.1,
121 margin: [0, 'auto'],
122 },
123 featureList: {
124 '& li': {
125 borderBottom: [1, 'solid', '#CECECE'],
126 },
127 },
128 fullFeatureList: {
129 marginTop: 40,
130 textAlign: 'center',
131 display: 'block',
132 color: `${theme.styleTypes.primary.contrast} !important`,
133 },
134});
135
136@injectSheet(styles) @observer
137class PlanSelection extends Component {
138 static propTypes = {
139 classes: PropTypes.object.isRequired,
140 firstname: PropTypes.string.isRequired,
141 plans: PropTypes.object.isRequired,
142 currency: PropTypes.string.isRequired,
143 subscriptionExpired: PropTypes.bool.isRequired,
144 upgradeAccount: PropTypes.func.isRequired,
145 stayOnFree: PropTypes.func.isRequired,
146 hadSubscription: PropTypes.bool.isRequired,
147 };
148
149 static contextTypes = {
150 intl: intlShape,
151 };
152
153 render() {
154 const {
155 classes,
156 firstname,
157 plans,
158 currency,
159 subscriptionExpired,
160 upgradeAccount,
161 stayOnFree,
162 hadSubscription,
163 } = this.props;
164
165 const { intl } = this.context;
166
167 return (
168 <div
169 className={classes.root}
170 >
171 <div className={classes.container}>
172 <div className={classes.bigIcon}>
173 <Icon icon={mdiRocket} />
174 </div>
175 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
176 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
177 <div className={classes.plans}>
178 <PlanItem
179 name={i18nPlanName(PLANS.FREE, intl)}
180 text={intl.formatMessage(messages.textFree)}
181 price={0}
182 currency={currency}
183 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
184 upgrade={() => stayOnFree()}
185 simpleCTA
186 >
187 <FeatureList
188 plan={PLANS.FREE}
189 className={classes.featureList}
190 />
191 </PlanItem>
192 <PlanItem
193 name={i18nPlanName(plans.personal.yearly.id, intl)}
194 text={intl.formatMessage(messages.textPersonal)}
195 price={plans.personal.yearly.price}
196 currency={currency}
197 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
198 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
199 >
200 <FeatureList
201 plan={PLANS.PERSONAL}
202 className={classes.featureList}
203 />
204 </PlanItem>
205 <PlanItem
206 name={i18nPlanName(plans.pro.yearly.id, intl)}
207 text={intl.formatMessage(messages.textProfessional)}
208 price={plans.pro.yearly.price}
209 currency={currency}
210 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
211 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
212 perUser
213 >
214 <FeatureList
215 plan={PLANS.PRO}
216 className={classes.featureList}
217 />
218 </PlanItem>
219 </div>
220 <a
221 href="https://meetfranz.com/pricing"
222 target="_blank"
223 className={classes.fullFeatureList}
224 >
225 {intl.formatMessage(messages.fullFeatureList)}
226 </a>
227 </div>
228 </div>
229 );
230 }
231}
232
233export default PlanSelection;