aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/ui
diff options
context:
space:
mode:
authorLibravatar vantezzen <properly@protonmail.com>2019-09-07 15:50:23 +0200
committerLibravatar vantezzen <properly@protonmail.com>2019-09-07 15:50:23 +0200
commite7a74514c1e7c3833dfdcf5900cb87f9e6e8354e (patch)
treeb8314e4155503b135dcb07e8b4a0e847e25c19cf /src/components/ui
parentUpdate CHANGELOG.md (diff)
parentUpdate CHANGELOG.md (diff)
downloadferdium-app-e7a74514c1e7c3833dfdcf5900cb87f9e6e8354e.tar.gz
ferdium-app-e7a74514c1e7c3833dfdcf5900cb87f9e6e8354e.tar.zst
ferdium-app-e7a74514c1e7c3833dfdcf5900cb87f9e6e8354e.zip
Merge branch 'master' of https://github.com/meetfranz/franz into franz-5.3.0
Diffstat (limited to 'src/components/ui')
-rw-r--r--src/components/ui/ActivateTrialButton/index.js125
-rw-r--r--src/components/ui/FeatureItem.js37
-rw-r--r--src/components/ui/FeatureList.js89
-rw-r--r--src/components/ui/Modal/index.js3
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js22
-rw-r--r--src/components/ui/UpgradeButton/index.js89
6 files changed, 361 insertions, 4 deletions
diff --git a/src/components/ui/ActivateTrialButton/index.js b/src/components/ui/ActivateTrialButton/index.js
new file mode 100644
index 000000000..e0637da90
--- /dev/null
+++ b/src/components/ui/ActivateTrialButton/index.js
@@ -0,0 +1,125 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import classnames from 'classnames';
6
7import { Button } from '@meetfranz/forms';
8import { gaEvent } from '../../../lib/analytics';
9
10import UserStore from '../../../stores/UserStore';
11
12const messages = defineMessages({
13 action: {
14 id: 'feature.delayApp.upgrade.action',
15 defaultMessage: '!!!Get a Franz Supporter License',
16 },
17 actionTrial: {
18 id: 'feature.delayApp.trial.action',
19 defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional',
20 },
21 shortAction: {
22 id: 'feature.delayApp.upgrade.actionShort',
23 defaultMessage: '!!!Upgrade account',
24 },
25 shortActionTrial: {
26 id: 'feature.delayApp.trial.actionShort',
27 defaultMessage: '!!!Activate the free Franz Professional trial',
28 },
29 noStringsAttachedHeadline: {
30 id: 'pricing.trial.terms.headline',
31 defaultMessage: '!!!No strings attached',
32 },
33 noCreditCard: {
34 id: 'pricing.trial.terms.noCreditCard',
35 defaultMessage: '!!!No credit card required',
36 },
37 automaticTrialEnd: {
38 id: 'pricing.trial.terms.automaticTrialEnd',
39 defaultMessage: '!!!Your free trial ends automatically after 14 days',
40 },
41});
42
43@inject('stores', 'actions') @observer
44class ActivateTrialButton extends Component {
45 static propTypes = {
46 className: PropTypes.string,
47 short: PropTypes.bool,
48 gaEventInfo: PropTypes.shape({
49 category: PropTypes.string.isRequired,
50 event: PropTypes.string.isRequired,
51 label: PropTypes.string,
52 }),
53 };
54
55 static defaultProps = {
56 className: '',
57 short: false,
58 gaEventInfo: null,
59 }
60
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 handleCTAClick() {
66 const { actions, stores, gaEventInfo } = this.props;
67 const { hadSubscription } = stores.user.data;
68 // const { defaultTrialPlan } = stores.features.features;
69
70 let label = '';
71 if (!hadSubscription) {
72 // actions.user.activateTrial({ planId: defaultTrialPlan });
73
74 label = 'Start Trial';
75 } else {
76 label = 'Upgrade Account';
77 }
78
79 actions.ui.openSettings({ path: 'user' });
80
81 if (gaEventInfo) {
82 const { category, event } = gaEventInfo;
83 gaEvent(category, event, label);
84 }
85 }
86
87 render() {
88 const { stores, className, short } = this.props;
89 const { intl } = this.context;
90
91 const { hadSubscription } = stores.user.data;
92
93 let label;
94 if (hadSubscription) {
95 label = short ? messages.shortAction : messages.action;
96 } else {
97 label = short ? messages.shortActionTrial : messages.actionTrial;
98 }
99
100 return (
101 <Button
102 label={intl.formatMessage(label)}
103 className={classnames({
104 [className]: className,
105 })}
106 buttonType="inverted"
107 onClick={this.handleCTAClick.bind(this)}
108 busy={stores.user.activateTrialRequest.isExecuting}
109 />
110 );
111 }
112}
113
114export default ActivateTrialButton;
115
116ActivateTrialButton.wrappedComponent.propTypes = {
117 stores: PropTypes.shape({
118 user: PropTypes.instanceOf(UserStore).isRequired,
119 }).isRequired,
120 actions: PropTypes.shape({
121 ui: PropTypes.shape({
122 openSettings: PropTypes.func.isRequired,
123 }).isRequired,
124 }).isRequired,
125};
diff --git a/src/components/ui/FeatureItem.js b/src/components/ui/FeatureItem.js
new file mode 100644
index 000000000..7c482c4d4
--- /dev/null
+++ b/src/components/ui/FeatureItem.js
@@ -0,0 +1,37 @@
1import React from 'react';
2import injectSheet from 'react-jss';
3import { Icon } from '@meetfranz/ui';
4import classnames from 'classnames';
5import { mdiCheckCircle } from '@mdi/js';
6
7const styles = theme => ({
8 featureItem: {
9 borderBottom: [1, 'solid', theme.defaultContentBorder],
10 padding: [8, 0],
11 display: 'flex',
12 alignItems: 'center',
13 },
14 featureIcon: {
15 fill: theme.brandSuccess,
16 marginRight: 10,
17 },
18});
19
20export const FeatureItem = injectSheet(styles)(({
21 classes, className, name, icon,
22}) => (
23 <li className={classnames({
24 [classes.featureItem]: true,
25 [className]: className,
26 })}
27 >
28 {icon ? (
29 <span className={classes.featureIcon}>{icon}</span>
30 ) : (
31 <Icon icon={mdiCheckCircle} className={classes.featureIcon} size={1.5} />
32 )}
33 {name}
34 </li>
35));
36
37export 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;
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index 0b7154760..63d858c47 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -5,6 +5,7 @@ import classnames from 'classnames';
5import injectCSS from 'react-jss'; 5import injectCSS from 'react-jss';
6import { Icon } from '@meetfranz/ui'; 6import { Icon } from '@meetfranz/ui';
7 7
8import { mdiClose } from '@mdi/js';
8import styles from './styles'; 9import styles from './styles';
9 10
10// ReactModal.setAppElement('#root'); 11// ReactModal.setAppElement('#root');
@@ -59,7 +60,7 @@ export default @injectCSS(styles) class Modal extends Component {
59 className={classes.close} 60 className={classes.close}
60 onClick={close} 61 onClick={close}
61 > 62 >
62 <Icon icon="mdiClose" size={1.5} /> 63 <Icon icon={mdiClose} size={1.5} />
63 </button> 64 </button>
64 )} 65 )}
65 <div className={classes.content}> 66 <div className={classes.content}>
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
index b890b09ab..c53d345a0 100644
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ b/src/components/ui/PremiumFeatureContainer/index.js
@@ -9,6 +9,7 @@ import { oneOrManyChildElements } from '../../../prop-types';
9import UserStore from '../../../stores/UserStore'; 9import UserStore from '../../../stores/UserStore';
10 10
11import styles from './styles'; 11import styles from './styles';
12import { FeatureStore } from '../../../features/utils/FeatureStore';
12 13
13const messages = defineMessages({ 14const messages = defineMessages({
14 action: { 15 action: {
@@ -21,7 +22,10 @@ const messages = defineMessages({
21class PremiumFeatureContainer extends Component { 22class PremiumFeatureContainer extends Component {
22 static propTypes = { 23 static propTypes = {
23 classes: PropTypes.object.isRequired, 24 classes: PropTypes.object.isRequired,
24 condition: PropTypes.bool, 25 condition: PropTypes.oneOfType([
26 PropTypes.bool,
27 PropTypes.func,
28 ]),
25 gaEventInfo: PropTypes.shape({ 29 gaEventInfo: PropTypes.shape({
26 category: PropTypes.string.isRequired, 30 category: PropTypes.string.isRequired,
27 event: PropTypes.string.isRequired, 31 event: PropTypes.string.isRequired,
@@ -30,7 +34,7 @@ class PremiumFeatureContainer extends Component {
30 }; 34 };
31 35
32 static defaultProps = { 36 static defaultProps = {
33 condition: true, 37 condition: null,
34 gaEventInfo: null, 38 gaEventInfo: null,
35 }; 39 };
36 40
@@ -49,7 +53,18 @@ class PremiumFeatureContainer extends Component {
49 53
50 const { intl } = this.context; 54 const { intl } = this.context;
51 55
52 return !stores.user.data.isPremium && !!condition ? ( 56 let showWrapper = !!condition;
57
58 if (condition === null) {
59 showWrapper = !stores.user.data.isPremium;
60 } else if (typeof condition === 'function') {
61 showWrapper = condition({
62 isPremium: stores.user.data.isPremium,
63 features: stores.features.features,
64 });
65 }
66
67 return showWrapper ? (
53 <div className={classes.container}> 68 <div className={classes.container}>
54 <div className={classes.titleContainer}> 69 <div className={classes.titleContainer}>
55 <p className={classes.title}>Premium Feature</p> 70 <p className={classes.title}>Premium Feature</p>
@@ -75,6 +90,7 @@ PremiumFeatureContainer.wrappedComponent.propTypes = {
75 children: oneOrManyChildElements.isRequired, 90 children: oneOrManyChildElements.isRequired,
76 stores: PropTypes.shape({ 91 stores: PropTypes.shape({
77 user: PropTypes.instanceOf(UserStore).isRequired, 92 user: PropTypes.instanceOf(UserStore).isRequired,
93 features: PropTypes.instanceOf(FeatureStore).isRequired,
78 }).isRequired, 94 }).isRequired,
79 actions: PropTypes.shape({ 95 actions: PropTypes.shape({
80 ui: PropTypes.shape({ 96 ui: PropTypes.shape({
diff --git a/src/components/ui/UpgradeButton/index.js b/src/components/ui/UpgradeButton/index.js
new file mode 100644
index 000000000..73762f0bf
--- /dev/null
+++ b/src/components/ui/UpgradeButton/index.js
@@ -0,0 +1,89 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { Button } from '@meetfranz/forms';
7import { gaEvent } from '../../../lib/analytics';
8
9import UserStore from '../../../stores/UserStore';
10import ActivateTrialButton from '../ActivateTrialButton';
11
12const messages = defineMessages({
13 upgradeToPro: {
14 id: 'global.upgradeButton.upgradeToPro',
15 defaultMessage: '!!!Upgrade to Franz Professional',
16 },
17});
18
19@inject('stores', 'actions') @observer
20class UpgradeButton extends Component {
21 static propTypes = {
22 // eslint-disable-next-line
23 classes: PropTypes.object.isRequired,
24 className: PropTypes.string,
25 gaEventInfo: PropTypes.shape({
26 category: PropTypes.string.isRequired,
27 event: PropTypes.string.isRequired,
28 label: PropTypes.string,
29 }),
30 requiresPro: PropTypes.bool,
31 };
32
33 static defaultProps = {
34 className: '',
35 gaEventInfo: null,
36 requiresPro: false,
37 }
38
39 static contextTypes = {
40 intl: intlShape,
41 };
42
43 handleCTAClick() {
44 const { actions, gaEventInfo } = this.props;
45
46 actions.ui.openSettings({ path: 'user' });
47 if (gaEventInfo) {
48 const { category, event } = gaEventInfo;
49 gaEvent(category, event, 'Upgrade Account');
50 }
51 }
52
53 render() {
54 const { stores, requiresPro } = this.props;
55 const { intl } = this.context;
56
57 const { isPremium, isPersonal } = stores.user;
58
59 if (isPremium && isPersonal && requiresPro) {
60 return (
61 <Button
62 label={intl.formatMessage(messages.upgradeToPro)}
63 onClick={this.handleCTAClick.bind(this)}
64 className={this.props.className}
65 buttonType="inverted"
66 />
67 );
68 }
69
70 if (!isPremium) {
71 return <ActivateTrialButton {...this.props} />;
72 }
73
74 return null;
75 }
76}
77
78export default UpgradeButton;
79
80UpgradeButton.wrappedComponent.propTypes = {
81 stores: PropTypes.shape({
82 user: PropTypes.instanceOf(UserStore).isRequired,
83 }).isRequired,
84 actions: PropTypes.shape({
85 ui: PropTypes.shape({
86 openSettings: PropTypes.func.isRequired,
87 }).isRequired,
88 }).isRequired,
89};