diff options
author | vantezzen <properly@protonmail.com> | 2019-09-07 15:50:23 +0200 |
---|---|---|
committer | vantezzen <properly@protonmail.com> | 2019-09-07 15:50:23 +0200 |
commit | e7a74514c1e7c3833dfdcf5900cb87f9e6e8354e (patch) | |
tree | b8314e4155503b135dcb07e8b4a0e847e25c19cf /src/components/ui | |
parent | Update CHANGELOG.md (diff) | |
parent | Update CHANGELOG.md (diff) | |
download | ferdium-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.js | 125 | ||||
-rw-r--r-- | src/components/ui/FeatureItem.js | 37 | ||||
-rw-r--r-- | src/components/ui/FeatureList.js | 89 | ||||
-rw-r--r-- | src/components/ui/Modal/index.js | 3 | ||||
-rw-r--r-- | src/components/ui/PremiumFeatureContainer/index.js | 22 | ||||
-rw-r--r-- | src/components/ui/UpgradeButton/index.js | 89 |
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 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import classnames from 'classnames'; | ||
6 | |||
7 | import { Button } from '@meetfranz/forms'; | ||
8 | import { gaEvent } from '../../../lib/analytics'; | ||
9 | |||
10 | import UserStore from '../../../stores/UserStore'; | ||
11 | |||
12 | const 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 | ||
44 | class 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 | |||
114 | export default ActivateTrialButton; | ||
115 | |||
116 | ActivateTrialButton.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 @@ | |||
1 | import React from 'react'; | ||
2 | import injectSheet from 'react-jss'; | ||
3 | import { Icon } from '@meetfranz/ui'; | ||
4 | import classnames from 'classnames'; | ||
5 | import { mdiCheckCircle } from '@mdi/js'; | ||
6 | |||
7 | const 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 | |||
20 | export 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 | |||
37 | 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; | ||
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'; | |||
5 | import injectCSS from 'react-jss'; | 5 | import injectCSS from 'react-jss'; |
6 | import { Icon } from '@meetfranz/ui'; | 6 | import { Icon } from '@meetfranz/ui'; |
7 | 7 | ||
8 | import { mdiClose } from '@mdi/js'; | ||
8 | import styles from './styles'; | 9 | import 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'; | |||
9 | import UserStore from '../../../stores/UserStore'; | 9 | import UserStore from '../../../stores/UserStore'; |
10 | 10 | ||
11 | import styles from './styles'; | 11 | import styles from './styles'; |
12 | import { FeatureStore } from '../../../features/utils/FeatureStore'; | ||
12 | 13 | ||
13 | const messages = defineMessages({ | 14 | const messages = defineMessages({ |
14 | action: { | 15 | action: { |
@@ -21,7 +22,10 @@ const messages = defineMessages({ | |||
21 | class PremiumFeatureContainer extends Component { | 22 | class 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 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { inject, observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | |||
6 | import { Button } from '@meetfranz/forms'; | ||
7 | import { gaEvent } from '../../../lib/analytics'; | ||
8 | |||
9 | import UserStore from '../../../stores/UserStore'; | ||
10 | import ActivateTrialButton from '../ActivateTrialButton'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | upgradeToPro: { | ||
14 | id: 'global.upgradeButton.upgradeToPro', | ||
15 | defaultMessage: '!!!Upgrade to Franz Professional', | ||
16 | }, | ||
17 | }); | ||
18 | |||
19 | @inject('stores', 'actions') @observer | ||
20 | class 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 | |||
78 | export default UpgradeButton; | ||
79 | |||
80 | UpgradeButton.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 | }; | ||