diff options
Diffstat (limited to 'src/features')
-rw-r--r-- | src/features/basicAuth/Component.js | 1 | ||||
-rw-r--r-- | src/features/communityRecipes/index.js | 28 | ||||
-rw-r--r-- | src/features/communityRecipes/store.js | 31 | ||||
-rw-r--r-- | src/features/delayApp/Component.js | 43 | ||||
-rw-r--r-- | src/features/delayApp/index.js | 2 | ||||
-rw-r--r-- | src/features/serviceLimit/components/LimitReachedInfobox.js | 78 | ||||
-rw-r--r-- | src/features/serviceLimit/index.js | 33 | ||||
-rw-r--r-- | src/features/serviceLimit/store.js | 41 | ||||
-rw-r--r-- | src/features/serviceProxy/index.js | 8 | ||||
-rw-r--r-- | src/features/shareFranz/Component.js | 13 | ||||
-rw-r--r-- | src/features/spellchecker/index.js | 8 | ||||
-rw-r--r-- | src/features/todos/components/TodosWebview.js | 105 | ||||
-rw-r--r-- | src/features/todos/containers/TodosScreen.js | 13 | ||||
-rw-r--r-- | src/features/todos/store.js | 2 | ||||
-rw-r--r-- | src/features/workspaces/components/WorkspaceDrawer.js | 7 | ||||
-rw-r--r-- | src/features/workspaces/components/WorkspaceSwitchingIndicator.js | 8 | ||||
-rw-r--r-- | src/features/workspaces/components/WorkspacesDashboard.js | 50 | ||||
-rw-r--r-- | src/features/workspaces/store.js | 9 |
18 files changed, 411 insertions, 69 deletions
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js index a8252acb7..ba9ae2273 100644 --- a/src/features/basicAuth/Component.js +++ b/src/features/basicAuth/Component.js | |||
@@ -27,7 +27,6 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo | |||
27 | e.preventDefault(); | 27 | e.preventDefault(); |
28 | 28 | ||
29 | const values = Form.values(); | 29 | const values = Form.values(); |
30 | console.log('form submit', values); | ||
31 | 30 | ||
32 | sendCredentials(values.user, values.password); | 31 | sendCredentials(values.user, values.password); |
33 | resetState(); | 32 | resetState(); |
diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.js new file mode 100644 index 000000000..4d050f90e --- /dev/null +++ b/src/features/communityRecipes/index.js | |||
@@ -0,0 +1,28 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import { CommunityRecipesStore } from './store'; | ||
3 | |||
4 | const debug = require('debug')('Franz:feature:communityRecipes'); | ||
5 | |||
6 | export const DEFAULT_SERVICE_LIMIT = 3; | ||
7 | |||
8 | export const communityRecipesStore = new CommunityRecipesStore(); | ||
9 | |||
10 | export default function initCommunityRecipes(stores, actions) { | ||
11 | const { features } = stores; | ||
12 | |||
13 | communityRecipesStore.start(stores, actions); | ||
14 | |||
15 | // Toggle communityRecipe premium status | ||
16 | reaction( | ||
17 | () => ( | ||
18 | features.features.isCommunityRecipesIncludedInCurrentPlan | ||
19 | ), | ||
20 | (isPremiumFeature) => { | ||
21 | debug('Community recipes is premium feature: ', isPremiumFeature); | ||
22 | communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan = isPremiumFeature; | ||
23 | }, | ||
24 | { | ||
25 | fireImmediately: true, | ||
26 | }, | ||
27 | ); | ||
28 | } | ||
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js new file mode 100644 index 000000000..4d45c3b33 --- /dev/null +++ b/src/features/communityRecipes/store.js | |||
@@ -0,0 +1,31 @@ | |||
1 | import { computed, observable } from 'mobx'; | ||
2 | import { FeatureStore } from '../utils/FeatureStore'; | ||
3 | |||
4 | const debug = require('debug')('Franz:feature:communityRecipes:store'); | ||
5 | |||
6 | export class CommunityRecipesStore extends FeatureStore { | ||
7 | @observable isCommunityRecipesIncludedInCurrentPlan = false; | ||
8 | |||
9 | start(stores, actions) { | ||
10 | debug('start'); | ||
11 | this.stores = stores; | ||
12 | this.actions = actions; | ||
13 | } | ||
14 | |||
15 | stop() { | ||
16 | debug('stop'); | ||
17 | super.stop(); | ||
18 | } | ||
19 | |||
20 | @computed get communityRecipes() { | ||
21 | if (!this.stores) return []; | ||
22 | |||
23 | return this.stores.recipePreviews.dev.map((r) => { | ||
24 | r.isDevRecipe = !!r.author.find(a => a.email === this.stores.user.data.email); | ||
25 | |||
26 | return r; | ||
27 | }); | ||
28 | } | ||
29 | } | ||
30 | |||
31 | export default CommunityRecipesStore; | ||
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js index ff0f1f2f8..de5653f04 100644 --- a/src/features/delayApp/Component.js +++ b/src/features/delayApp/Component.js | |||
@@ -4,29 +4,39 @@ import { inject, 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 | import injectSheet from 'react-jss'; |
6 | 6 | ||
7 | import { Button } from '@meetfranz/forms'; | ||
7 | import { gaEvent } from '../../lib/analytics'; | 8 | import { gaEvent } from '../../lib/analytics'; |
8 | 9 | ||
9 | import Button from '../../components/ui/Button'; | 10 | // import Button from '../../components/ui/Button'; |
10 | 11 | ||
11 | import { config } from '.'; | 12 | import { config } from '.'; |
12 | import styles from './styles'; | 13 | import styles from './styles'; |
14 | import UserStore from '../../stores/UserStore'; | ||
13 | 15 | ||
14 | const messages = defineMessages({ | 16 | const messages = defineMessages({ |
15 | headline: { | 17 | headline: { |
16 | id: 'feature.delayApp.headline', | 18 | id: 'feature.delayApp.headline', |
17 | defaultMessage: '!!!Please purchase license to skip waiting', | 19 | defaultMessage: '!!!Please purchase license to skip waiting', |
18 | }, | 20 | }, |
21 | headlineTrial: { | ||
22 | id: 'feature.delayApp.trial.headline', | ||
23 | defaultMessage: '!!!Get the free Franz Professional 14 day trial and skip the line', | ||
24 | }, | ||
19 | action: { | 25 | action: { |
20 | id: 'feature.delayApp.action', | 26 | id: 'feature.delayApp.upgrade.action', |
21 | defaultMessage: '!!!Get a Franz Supporter License', | 27 | defaultMessage: '!!!Get a Franz Supporter License', |
22 | }, | 28 | }, |
29 | actionTrial: { | ||
30 | id: 'feature.delayApp.trial.action', | ||
31 | defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional', | ||
32 | }, | ||
23 | text: { | 33 | text: { |
24 | id: 'feature.delayApp.text', | 34 | id: 'feature.delayApp.text', |
25 | defaultMessage: '!!!Franz will continue in {seconds} seconds.', | 35 | defaultMessage: '!!!Franz will continue in {seconds} seconds.', |
26 | }, | 36 | }, |
27 | }); | 37 | }); |
28 | 38 | ||
29 | export default @inject('actions') @injectSheet(styles) @observer class DelayApp extends Component { | 39 | export default @inject('stores', 'actions') @injectSheet(styles) @observer class DelayApp extends Component { |
30 | static propTypes = { | 40 | static propTypes = { |
31 | // eslint-disable-next-line | 41 | // eslint-disable-next-line |
32 | classes: PropTypes.object.isRequired, | 42 | classes: PropTypes.object.isRequired, |
@@ -62,25 +72,37 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp | |||
62 | } | 72 | } |
63 | 73 | ||
64 | handleCTAClick() { | 74 | handleCTAClick() { |
65 | const { actions } = this.props; | 75 | const { actions, stores } = this.props; |
76 | const { hadSubscription } = stores.user.data; | ||
77 | const { defaultTrialPlan } = stores.features.features; | ||
78 | |||
79 | if (!hadSubscription) { | ||
80 | console.log('directly activate trial'); | ||
81 | actions.user.activateTrial({ planId: defaultTrialPlan }); | ||
66 | 82 | ||
67 | actions.ui.openSettings({ path: 'user' }); | 83 | gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); |
84 | } else { | ||
85 | actions.ui.openSettings({ path: 'user' }); | ||
68 | 86 | ||
69 | gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); | 87 | gaEvent('DelayApp', 'subscribe_click', 'Delay App Feature'); |
88 | } | ||
70 | } | 89 | } |
71 | 90 | ||
72 | render() { | 91 | render() { |
73 | const { classes } = this.props; | 92 | const { classes, stores } = this.props; |
74 | const { intl } = this.context; | 93 | const { intl } = this.context; |
75 | 94 | ||
95 | const { hadSubscription } = stores.user.data; | ||
96 | |||
76 | return ( | 97 | return ( |
77 | <div className={`${classes.container}`}> | 98 | <div className={`${classes.container}`}> |
78 | <h1 className={classes.headline}>{intl.formatMessage(messages.headline)}</h1> | 99 | <h1 className={classes.headline}>{intl.formatMessage(hadSubscription ? messages.headline : messages.headlineTrial)}</h1> |
79 | <Button | 100 | <Button |
80 | label={intl.formatMessage(messages.action)} | 101 | label={intl.formatMessage(hadSubscription ? messages.action : messages.actionTrial)} |
81 | className={classes.button} | 102 | className={classes.button} |
82 | buttonType="inverted" | 103 | buttonType="inverted" |
83 | onClick={this.handleCTAClick.bind(this)} | 104 | onClick={this.handleCTAClick.bind(this)} |
105 | busy={stores.user.activateTrialRequest.isExecuting} | ||
84 | /> | 106 | /> |
85 | <p className="footnote"> | 107 | <p className="footnote"> |
86 | {intl.formatMessage(messages.text, { | 108 | {intl.formatMessage(messages.text, { |
@@ -93,6 +115,9 @@ export default @inject('actions') @injectSheet(styles) @observer class DelayApp | |||
93 | } | 115 | } |
94 | 116 | ||
95 | DelayApp.wrappedComponent.propTypes = { | 117 | DelayApp.wrappedComponent.propTypes = { |
118 | stores: PropTypes.shape({ | ||
119 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
120 | }).isRequired, | ||
96 | actions: PropTypes.shape({ | 121 | actions: PropTypes.shape({ |
97 | ui: PropTypes.shape({ | 122 | ui: PropTypes.shape({ |
98 | openSettings: PropTypes.func.isRequired, | 123 | openSettings: PropTypes.func.isRequired, |
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js index 39fae3b20..627537de7 100644 --- a/src/features/delayApp/index.js +++ b/src/features/delayApp/index.js | |||
@@ -44,7 +44,7 @@ export default function init(stores) { | |||
44 | config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; | 44 | config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; |
45 | 45 | ||
46 | autorun(() => { | 46 | autorun(() => { |
47 | if (stores.services.all.length === 0) { | 47 | if (stores.services.allDisplayed.length === 0) { |
48 | debug('seas', stores.services.all.length); | 48 | debug('seas', stores.services.all.length); |
49 | shownAfterLaunch = true; | 49 | shownAfterLaunch = true; |
50 | return; | 50 | return; |
diff --git a/src/features/serviceLimit/components/LimitReachedInfobox.js b/src/features/serviceLimit/components/LimitReachedInfobox.js new file mode 100644 index 000000000..fc54dcf85 --- /dev/null +++ b/src/features/serviceLimit/components/LimitReachedInfobox.js | |||
@@ -0,0 +1,78 @@ | |||
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 injectSheet from 'react-jss'; | ||
6 | import { Infobox } from '@meetfranz/ui'; | ||
7 | |||
8 | import { gaEvent } from '../../../lib/analytics'; | ||
9 | |||
10 | const messages = defineMessages({ | ||
11 | limitReached: { | ||
12 | id: 'feature.serviceLimit.limitReached', | ||
13 | defaultMessage: '!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.', | ||
14 | }, | ||
15 | action: { | ||
16 | id: 'premiumFeature.button.upgradeAccount', | ||
17 | defaultMessage: '!!!Upgrade account', | ||
18 | }, | ||
19 | }); | ||
20 | |||
21 | const styles = theme => ({ | ||
22 | container: { | ||
23 | height: 'auto', | ||
24 | background: theme.styleTypes.primary.accent, | ||
25 | color: theme.styleTypes.primary.contrast, | ||
26 | borderRadius: 0, | ||
27 | marginBottom: 0, | ||
28 | |||
29 | '& > div': { | ||
30 | marginBottom: 0, | ||
31 | }, | ||
32 | |||
33 | '& button': { | ||
34 | color: theme.styleTypes.primary.contrast, | ||
35 | }, | ||
36 | }, | ||
37 | }); | ||
38 | |||
39 | |||
40 | @inject('stores', 'actions') @injectSheet(styles) @observer | ||
41 | class LimitReachedInfobox extends Component { | ||
42 | static propTypes = { | ||
43 | classes: PropTypes.object.isRequired, | ||
44 | stores: PropTypes.object.isRequired, | ||
45 | actions: PropTypes.object.isRequired, | ||
46 | }; | ||
47 | |||
48 | static contextTypes = { | ||
49 | intl: intlShape, | ||
50 | }; | ||
51 | |||
52 | render() { | ||
53 | const { classes, stores, actions } = this.props; | ||
54 | const { intl } = this.context; | ||
55 | |||
56 | const { | ||
57 | serviceLimit, | ||
58 | } = stores; | ||
59 | |||
60 | if (!serviceLimit.userHasReachedServiceLimit) return null; | ||
61 | |||
62 | return ( | ||
63 | <Infobox | ||
64 | icon="mdiInformation" | ||
65 | className={classes.container} | ||
66 | ctaLabel={intl.formatMessage(messages.action)} | ||
67 | ctaOnClick={() => { | ||
68 | actions.ui.openSettings({ path: 'user' }); | ||
69 | gaEvent('Service Limit', 'upgrade', 'Upgrade account'); | ||
70 | }} | ||
71 | > | ||
72 | {intl.formatMessage(messages.limitReached, { amount: serviceLimit.serviceCount, limit: serviceLimit.serviceLimit })} | ||
73 | </Infobox> | ||
74 | ); | ||
75 | } | ||
76 | } | ||
77 | |||
78 | export default LimitReachedInfobox; | ||
diff --git a/src/features/serviceLimit/index.js b/src/features/serviceLimit/index.js new file mode 100644 index 000000000..92ad8bb98 --- /dev/null +++ b/src/features/serviceLimit/index.js | |||
@@ -0,0 +1,33 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import { ServiceLimitStore } from './store'; | ||
3 | |||
4 | const debug = require('debug')('Franz:feature:serviceLimit'); | ||
5 | |||
6 | export const DEFAULT_SERVICE_LIMIT = 3; | ||
7 | |||
8 | let store = null; | ||
9 | |||
10 | export const serviceLimitStore = new ServiceLimitStore(); | ||
11 | |||
12 | export default function initServiceLimit(stores, actions) { | ||
13 | const { features } = stores; | ||
14 | |||
15 | // Toggle serviceLimit feature | ||
16 | reaction( | ||
17 | () => ( | ||
18 | features.features.isServiceLimitEnabled | ||
19 | ), | ||
20 | (isEnabled) => { | ||
21 | if (isEnabled) { | ||
22 | debug('Initializing `serviceLimit` feature'); | ||
23 | store = serviceLimitStore.start(stores, actions); | ||
24 | } else if (store) { | ||
25 | debug('Disabling `serviceLimit` feature'); | ||
26 | serviceLimitStore.stop(); | ||
27 | } | ||
28 | }, | ||
29 | { | ||
30 | fireImmediately: true, | ||
31 | }, | ||
32 | ); | ||
33 | } | ||
diff --git a/src/features/serviceLimit/store.js b/src/features/serviceLimit/store.js new file mode 100644 index 000000000..9836c5f51 --- /dev/null +++ b/src/features/serviceLimit/store.js | |||
@@ -0,0 +1,41 @@ | |||
1 | import { computed, observable } from 'mobx'; | ||
2 | import { FeatureStore } from '../utils/FeatureStore'; | ||
3 | import { DEFAULT_SERVICE_LIMIT } from '.'; | ||
4 | |||
5 | const debug = require('debug')('Franz:feature:serviceLimit:store'); | ||
6 | |||
7 | export class ServiceLimitStore extends FeatureStore { | ||
8 | @observable isServiceLimitEnabled = false; | ||
9 | |||
10 | start(stores, actions) { | ||
11 | debug('start'); | ||
12 | this.stores = stores; | ||
13 | this.actions = actions; | ||
14 | |||
15 | this.isServiceLimitEnabled = true; | ||
16 | } | ||
17 | |||
18 | stop() { | ||
19 | super.stop(); | ||
20 | |||
21 | this.isServiceLimitEnabled = false; | ||
22 | } | ||
23 | |||
24 | @computed get userHasReachedServiceLimit() { | ||
25 | if (!this.isServiceLimitEnabled) return false; | ||
26 | |||
27 | return this.serviceLimit !== 0 && this.serviceCount >= this.serviceLimit; | ||
28 | } | ||
29 | |||
30 | @computed get serviceLimit() { | ||
31 | if (!this.isServiceLimitEnabled || this.stores.features.features.serviceLimitCount === 0) return 0; | ||
32 | |||
33 | return this.stores.features.features.serviceLimitCount || DEFAULT_SERVICE_LIMIT; | ||
34 | } | ||
35 | |||
36 | @computed get serviceCount() { | ||
37 | return this.stores.services.all.length; | ||
38 | } | ||
39 | } | ||
40 | |||
41 | export default ServiceLimitStore; | ||
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js index 4bea327ad..55c600de4 100644 --- a/src/features/serviceProxy/index.js +++ b/src/features/serviceProxy/index.js | |||
@@ -9,17 +9,17 @@ const debug = require('debug')('Franz:feature:serviceProxy'); | |||
9 | 9 | ||
10 | export const config = observable({ | 10 | export const config = observable({ |
11 | isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled, | 11 | isEnabled: DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled, |
12 | isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature, | 12 | isPremium: DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan, |
13 | }); | 13 | }); |
14 | 14 | ||
15 | export default function init(stores) { | 15 | export default function init(stores) { |
16 | debug('Initializing `serviceProxy` feature'); | 16 | debug('Initializing `serviceProxy` feature'); |
17 | 17 | ||
18 | autorun(() => { | 18 | autorun(() => { |
19 | const { isServiceProxyEnabled, isServiceProxyPremiumFeature } = stores.features.features; | 19 | const { isServiceProxyEnabled, isServiceProxyIncludedInCurrentPlan } = stores.features.features; |
20 | 20 | ||
21 | config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled; | 21 | config.isEnabled = isServiceProxyEnabled !== undefined ? isServiceProxyEnabled : DEFAULT_FEATURES_CONFIG.isServiceProxyEnabled; |
22 | config.isPremium = isServiceProxyPremiumFeature !== undefined ? isServiceProxyPremiumFeature : DEFAULT_FEATURES_CONFIG.isServiceProxyPremiumFeature; | 22 | config.isIncludedInCurrentPlan = isServiceProxyIncludedInCurrentPlan !== undefined ? isServiceProxyIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isServiceProxyIncludedInCurrentPlan; |
23 | 23 | ||
24 | const services = stores.services.enabled; | 24 | const services = stores.services.enabled; |
25 | const isPremiumUser = stores.user.data.isPremium; | 25 | const isPremiumUser = stores.user.data.isPremium; |
@@ -30,7 +30,7 @@ export default function init(stores) { | |||
30 | services.forEach((service) => { | 30 | services.forEach((service) => { |
31 | const s = session.fromPartition(`persist:service-${service.id}`); | 31 | const s = session.fromPartition(`persist:service-${service.id}`); |
32 | 32 | ||
33 | if (config.isEnabled && (isPremiumUser || !config.isPremium)) { | 33 | if (config.isEnabled && (isPremiumUser || !config.isIncludedInCurrentPlan)) { |
34 | const serviceProxyConfig = proxySettings[service.id]; | 34 | const serviceProxyConfig = proxySettings[service.id]; |
35 | 35 | ||
36 | if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) { | 36 | if (serviceProxyConfig && serviceProxyConfig.isEnabled && serviceProxyConfig.host) { |
diff --git a/src/features/shareFranz/Component.js b/src/features/shareFranz/Component.js index 8d1d595c5..a33315e17 100644 --- a/src/features/shareFranz/Component.js +++ b/src/features/shareFranz/Component.js | |||
@@ -6,6 +6,9 @@ import { defineMessages, intlShape } from 'react-intl'; | |||
6 | import { Button } from '@meetfranz/forms'; | 6 | import { Button } from '@meetfranz/forms'; |
7 | import { H1, Icon } from '@meetfranz/ui'; | 7 | import { H1, Icon } from '@meetfranz/ui'; |
8 | 8 | ||
9 | import { | ||
10 | mdiHeart, mdiEmail, mdiFacebookBox, mdiTwitter, | ||
11 | } from '@mdi/js'; | ||
9 | import Modal from '../../components/ui/Modal'; | 12 | import Modal from '../../components/ui/Modal'; |
10 | import { state } from '.'; | 13 | import { state } from '.'; |
11 | import { gaEvent } from '../../lib/analytics'; | 14 | import { gaEvent } from '../../lib/analytics'; |
@@ -75,7 +78,7 @@ const styles = theme => ({ | |||
75 | }, | 78 | }, |
76 | cta: { | 79 | cta: { |
77 | background: theme.styleTypes.primary.contrast, | 80 | background: theme.styleTypes.primary.contrast, |
78 | color: theme.styleTypes.primary.accent, | 81 | color: `${theme.styleTypes.primary.accent} !important`, |
79 | 82 | ||
80 | '& svg': { | 83 | '& svg': { |
81 | fill: theme.styleTypes.primary.accent, | 84 | fill: theme.styleTypes.primary.accent, |
@@ -116,7 +119,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz | |||
116 | close={this.close.bind(this)} | 119 | close={this.close.bind(this)} |
117 | > | 120 | > |
118 | <div className={classes.heartContainer}> | 121 | <div className={classes.heartContainer}> |
119 | <Icon icon="mdiHeart" className={classes.heart} size={4} /> | 122 | <Icon icon={mdiHeart} className={classes.heart} size={4} /> |
120 | </div> | 123 | </div> |
121 | <H1 className={classes.headline}> | 124 | <H1 className={classes.headline}> |
122 | {intl.formatMessage(messages.headline)} | 125 | {intl.formatMessage(messages.headline)} |
@@ -126,7 +129,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz | |||
126 | <Button | 129 | <Button |
127 | label={intl.formatMessage(messages.actionsEmail)} | 130 | label={intl.formatMessage(messages.actionsEmail)} |
128 | className={classes.cta} | 131 | className={classes.cta} |
129 | icon="mdiEmail" | 132 | icon={mdiEmail} |
130 | href={`mailto:?subject=Meet the cool app Franz&body=${intl.formatMessage(messages.shareTextEmail, { count: serviceCount })}}`} | 133 | href={`mailto:?subject=Meet the cool app Franz&body=${intl.formatMessage(messages.shareTextEmail, { count: serviceCount })}}`} |
131 | target="_blank" | 134 | target="_blank" |
132 | onClick={() => { | 135 | onClick={() => { |
@@ -136,7 +139,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz | |||
136 | <Button | 139 | <Button |
137 | label={intl.formatMessage(messages.actionsFacebook)} | 140 | label={intl.formatMessage(messages.actionsFacebook)} |
138 | className={classes.cta} | 141 | className={classes.cta} |
139 | icon="mdiFacebookBox" | 142 | icon={mdiFacebookBox} |
140 | href="https://www.facebook.com/sharer/sharer.php?u=https://www.meetfranz.com?utm_source=facebook&utm_medium=referral&utm_campaign=share-button" | 143 | href="https://www.facebook.com/sharer/sharer.php?u=https://www.meetfranz.com?utm_source=facebook&utm_medium=referral&utm_campaign=share-button" |
141 | target="_blank" | 144 | target="_blank" |
142 | onClick={() => { | 145 | onClick={() => { |
@@ -146,7 +149,7 @@ export default @injectSheet(styles) @inject('stores') @observer class ShareFranz | |||
146 | <Button | 149 | <Button |
147 | label={intl.formatMessage(messages.actionsTwitter)} | 150 | label={intl.formatMessage(messages.actionsTwitter)} |
148 | className={classes.cta} | 151 | className={classes.cta} |
149 | icon="mdiTwitter" | 152 | icon={mdiTwitter} |
150 | href={`http://twitter.com/intent/tweet?status=${intl.formatMessage(messages.shareTextTwitter, { count: serviceCount })}`} | 153 | href={`http://twitter.com/intent/tweet?status=${intl.formatMessage(messages.shareTextTwitter, { count: serviceCount })}`} |
151 | target="_blank" | 154 | target="_blank" |
152 | onClick={() => { | 155 | onClick={() => { |
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js index 79a2172b4..a07f9f63a 100644 --- a/src/features/spellchecker/index.js +++ b/src/features/spellchecker/index.js | |||
@@ -5,18 +5,18 @@ import { DEFAULT_FEATURES_CONFIG } from '../../config'; | |||
5 | const debug = require('debug')('Franz:feature:spellchecker'); | 5 | const debug = require('debug')('Franz:feature:spellchecker'); |
6 | 6 | ||
7 | export const config = observable({ | 7 | export const config = observable({ |
8 | isPremium: DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature, | 8 | isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan, |
9 | }); | 9 | }); |
10 | 10 | ||
11 | export default function init(stores) { | 11 | export default function init(stores) { |
12 | debug('Initializing `spellchecker` feature'); | 12 | debug('Initializing `spellchecker` feature'); |
13 | 13 | ||
14 | autorun(() => { | 14 | autorun(() => { |
15 | const { isSpellcheckerPremiumFeature } = stores.features.features; | 15 | const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features; |
16 | 16 | ||
17 | config.isPremium = isSpellcheckerPremiumFeature !== undefined ? isSpellcheckerPremiumFeature : DEFAULT_FEATURES_CONFIG.isSpellcheckerPremiumFeature; | 17 | config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan; |
18 | 18 | ||
19 | if (!stores.user.data.isPremium && config.isPremium && stores.settings.app.enableSpellchecking) { | 19 | if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) { |
20 | debug('Override settings.spellcheckerEnabled flag to false'); | 20 | debug('Override settings.spellcheckerEnabled flag to false'); |
21 | 21 | ||
22 | Object.assign(stores.settings.app, { | 22 | Object.assign(stores.settings.app, { |
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index 9dd313109..143955a7b 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -4,12 +4,31 @@ import { observer } from 'mobx-react'; | |||
4 | import injectSheet from 'react-jss'; | 4 | import injectSheet from 'react-jss'; |
5 | import Webview from 'react-electron-web-view'; | 5 | import Webview from 'react-electron-web-view'; |
6 | import { Icon } from '@meetfranz/ui'; | 6 | import { Icon } from '@meetfranz/ui'; |
7 | import { defineMessages, intlShape } from 'react-intl'; | ||
7 | 8 | ||
9 | import { mdiChevronRight, mdiCheckAll } from '@mdi/js'; | ||
8 | import * as environment from '../../../environment'; | 10 | import * as environment from '../../../environment'; |
11 | import Appear from '../../../components/ui/effects/Appear'; | ||
12 | import ActivateTrialButton from '../../../components/ui/ActivateTrialButton'; | ||
9 | 13 | ||
10 | const OPEN_TODOS_BUTTON_SIZE = 45; | 14 | const OPEN_TODOS_BUTTON_SIZE = 45; |
11 | const CLOSE_TODOS_BUTTON_SIZE = 35; | 15 | const CLOSE_TODOS_BUTTON_SIZE = 35; |
12 | 16 | ||
17 | const messages = defineMessages({ | ||
18 | premiumInfo: { | ||
19 | id: 'feature.todos.premium.info', | ||
20 | defaultMessage: '!!!The Franz Todos Preview is currently only available for Franz Premium accounts.', | ||
21 | }, | ||
22 | upgradeCTA: { | ||
23 | id: 'feature.todos.premium.upgrade', | ||
24 | defaultMessage: '!!!Upgrade Account', | ||
25 | }, | ||
26 | rolloutInfo: { | ||
27 | id: 'feature.todos.premium.rollout', | ||
28 | defaultMessage: '!!!Franz Todos will be available to everyone soon.', | ||
29 | }, | ||
30 | }); | ||
31 | |||
13 | const styles = theme => ({ | 32 | const styles = theme => ({ |
14 | root: { | 33 | root: { |
15 | background: theme.colorBackground, | 34 | background: theme.colorBackground, |
@@ -47,7 +66,7 @@ const styles = theme => ({ | |||
47 | height: OPEN_TODOS_BUTTON_SIZE, | 66 | height: OPEN_TODOS_BUTTON_SIZE, |
48 | background: theme.todos.toggleButton.background, | 67 | background: theme.todos.toggleButton.background, |
49 | position: 'absolute', | 68 | position: 'absolute', |
50 | bottom: 80, | 69 | bottom: 120, |
51 | right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)), | 70 | right: props => (props.width + (props.isVisible ? -OPEN_TODOS_BUTTON_SIZE / 2 : 0)), |
52 | borderRadius: OPEN_TODOS_BUTTON_SIZE / 2, | 71 | borderRadius: OPEN_TODOS_BUTTON_SIZE / 2, |
53 | opacity: props => (props.isVisible ? 0 : 1), | 72 | opacity: props => (props.isVisible ? 0 : 1), |
@@ -71,10 +90,10 @@ const styles = theme => ({ | |||
71 | height: CLOSE_TODOS_BUTTON_SIZE, | 90 | height: CLOSE_TODOS_BUTTON_SIZE, |
72 | background: theme.todos.toggleButton.background, | 91 | background: theme.todos.toggleButton.background, |
73 | position: 'absolute', | 92 | position: 'absolute', |
74 | bottom: 80, | 93 | bottom: 120, |
75 | right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2), | 94 | right: ({ width }) => (width + -CLOSE_TODOS_BUTTON_SIZE / 2), |
76 | borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2, | 95 | borderRadius: CLOSE_TODOS_BUTTON_SIZE / 2, |
77 | opacity: 0, | 96 | opacity: ({ isTodosIncludedInCurrentPlan }) => (!isTodosIncludedInCurrentPlan ? 1 : 0), |
78 | transition: 'opacity 0.5s', | 97 | transition: 'opacity 0.5s', |
79 | zIndex: 600, | 98 | zIndex: 600, |
80 | display: 'flex', | 99 | display: 'flex', |
@@ -86,6 +105,26 @@ const styles = theme => ({ | |||
86 | fill: theme.todos.toggleButton.textColor, | 105 | fill: theme.todos.toggleButton.textColor, |
87 | }, | 106 | }, |
88 | }, | 107 | }, |
108 | premiumContainer: { | ||
109 | display: 'flex', | ||
110 | flexDirection: 'column', | ||
111 | justifyContent: 'center', | ||
112 | alignItems: 'center', | ||
113 | width: '80%', | ||
114 | maxWidth: 300, | ||
115 | margin: [-50, 'auto', 0], | ||
116 | textAlign: 'center', | ||
117 | }, | ||
118 | premiumIcon: { | ||
119 | marginBottom: 40, | ||
120 | background: theme.styleTypes.primary.accent, | ||
121 | fill: theme.styleTypes.primary.contrast, | ||
122 | padding: 10, | ||
123 | borderRadius: 10, | ||
124 | }, | ||
125 | premiumCTA: { | ||
126 | marginTop: 40, | ||
127 | }, | ||
89 | }); | 128 | }); |
90 | 129 | ||
91 | @injectSheet(styles) @observer | 130 | @injectSheet(styles) @observer |
@@ -99,6 +138,7 @@ class TodosWebview extends Component { | |||
99 | resize: PropTypes.func.isRequired, | 138 | resize: PropTypes.func.isRequired, |
100 | width: PropTypes.number.isRequired, | 139 | width: PropTypes.number.isRequired, |
101 | minWidth: PropTypes.number.isRequired, | 140 | minWidth: PropTypes.number.isRequired, |
141 | isTodosIncludedInCurrentPlan: PropTypes.bool.isRequired, | ||
102 | }; | 142 | }; |
103 | 143 | ||
104 | state = { | 144 | state = { |
@@ -106,6 +146,10 @@ class TodosWebview extends Component { | |||
106 | width: 300, | 146 | width: 300, |
107 | }; | 147 | }; |
108 | 148 | ||
149 | static contextTypes = { | ||
150 | intl: intlShape, | ||
151 | }; | ||
152 | |||
109 | componentWillMount() { | 153 | componentWillMount() { |
110 | const { width } = this.props; | 154 | const { width } = this.props; |
111 | 155 | ||
@@ -182,9 +226,19 @@ class TodosWebview extends Component { | |||
182 | 226 | ||
183 | render() { | 227 | render() { |
184 | const { | 228 | const { |
185 | classes, isVisible, togglePanel, | 229 | classes, |
230 | isVisible, | ||
231 | togglePanel, | ||
232 | isTodosIncludedInCurrentPlan, | ||
186 | } = this.props; | 233 | } = this.props; |
187 | const { width, delta, isDragging } = this.state; | 234 | |
235 | const { | ||
236 | width, | ||
237 | delta, | ||
238 | isDragging, | ||
239 | } = this.state; | ||
240 | |||
241 | const { intl } = this.context; | ||
188 | 242 | ||
189 | return ( | 243 | return ( |
190 | <div | 244 | <div |
@@ -198,7 +252,7 @@ class TodosWebview extends Component { | |||
198 | className={isVisible ? classes.closeTodosButton : classes.openTodosButton} | 252 | className={isVisible ? classes.closeTodosButton : classes.openTodosButton} |
199 | type="button" | 253 | type="button" |
200 | > | 254 | > |
201 | <Icon icon={isVisible ? 'mdiChevronRight' : 'mdiCheckAll'} size={2} /> | 255 | <Icon icon={isVisible ? mdiChevronRight : mdiCheckAll} size={2} /> |
202 | </button> | 256 | </button> |
203 | <div | 257 | <div |
204 | className={classes.resizeHandler} | 258 | className={classes.resizeHandler} |
@@ -211,18 +265,33 @@ class TodosWebview extends Component { | |||
211 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad | 265 | style={{ left: delta }} // This hack is required as resizing with webviews beneath behaves quite bad |
212 | /> | 266 | /> |
213 | )} | 267 | )} |
214 | <Webview | 268 | {isTodosIncludedInCurrentPlan ? ( |
215 | className={classes.webview} | 269 | <Webview |
216 | onDidAttach={() => { | 270 | className={classes.webview} |
217 | const { setTodosWebview } = this.props; | 271 | onDidAttach={() => { |
218 | setTodosWebview(this.webview); | 272 | const { setTodosWebview } = this.props; |
219 | this.startListeningToIpcMessages(); | 273 | setTodosWebview(this.webview); |
220 | }} | 274 | this.startListeningToIpcMessages(); |
221 | partition="persist:todos" | 275 | }} |
222 | preload="./features/todos/preload.js" | 276 | partition="persist:todos" |
223 | ref={(webview) => { this.webview = webview ? webview.view : null; }} | 277 | preload="./features/todos/preload.js" |
224 | src={environment.TODOS_FRONTEND} | 278 | ref={(webview) => { this.webview = webview ? webview.view : null; }} |
225 | /> | 279 | src={environment.TODOS_FRONTEND} |
280 | /> | ||
281 | ) : ( | ||
282 | <Appear> | ||
283 | <div className={classes.premiumContainer}> | ||
284 | <Icon icon={mdiCheckAll} className={classes.premiumIcon} size={5} /> | ||
285 | <p>{intl.formatMessage(messages.premiumInfo)}</p> | ||
286 | <p>{intl.formatMessage(messages.rolloutInfo)}</p> | ||
287 | <ActivateTrialButton | ||
288 | className={classes.premiumCTA} | ||
289 | gaEventInfo={{ category: 'Todos', event: 'upgrade' }} | ||
290 | short | ||
291 | /> | ||
292 | </div> | ||
293 | </Appear> | ||
294 | )} | ||
226 | </div> | 295 | </div> |
227 | ); | 296 | ); |
228 | } | 297 | } |
diff --git a/src/features/todos/containers/TodosScreen.js b/src/features/todos/containers/TodosScreen.js index d071d0677..65afc985b 100644 --- a/src/features/todos/containers/TodosScreen.js +++ b/src/features/todos/containers/TodosScreen.js | |||
@@ -1,12 +1,14 @@ | |||
1 | import React, { Component } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import { observer } from 'mobx-react'; | 2 | import { observer, inject } from 'mobx-react'; |
3 | import PropTypes from 'prop-types'; | ||
3 | 4 | ||
5 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
4 | import TodosWebview from '../components/TodosWebview'; | 6 | import TodosWebview from '../components/TodosWebview'; |
5 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | 7 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; |
6 | import { TODOS_MIN_WIDTH, todosStore } from '..'; | 8 | import { TODOS_MIN_WIDTH, todosStore } from '..'; |
7 | import { todoActions } from '../actions'; | 9 | import { todoActions } from '../actions'; |
8 | 10 | ||
9 | @observer | 11 | @inject('stores', 'actions') @observer |
10 | class TodosScreen extends Component { | 12 | class TodosScreen extends Component { |
11 | render() { | 13 | render() { |
12 | if (!todosStore || !todosStore.isFeatureActive) { | 14 | if (!todosStore || !todosStore.isFeatureActive) { |
@@ -23,6 +25,7 @@ class TodosScreen extends Component { | |||
23 | width={todosStore.width} | 25 | width={todosStore.width} |
24 | minWidth={TODOS_MIN_WIDTH} | 26 | minWidth={TODOS_MIN_WIDTH} |
25 | resize={width => todoActions.resize({ width })} | 27 | resize={width => todoActions.resize({ width })} |
28 | isTodosIncludedInCurrentPlan={this.props.stores.features.features.isTodosIncludedInCurrentPlan || false} | ||
26 | /> | 29 | /> |
27 | </ErrorBoundary> | 30 | </ErrorBoundary> |
28 | ); | 31 | ); |
@@ -30,3 +33,9 @@ class TodosScreen extends Component { | |||
30 | } | 33 | } |
31 | 34 | ||
32 | export default TodosScreen; | 35 | export default TodosScreen; |
36 | |||
37 | TodosScreen.wrappedComponent.propTypes = { | ||
38 | stores: PropTypes.shape({ | ||
39 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
40 | }).isRequired, | ||
41 | }; | ||
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index 242b38bf7..170408ebb 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js | |||
@@ -12,6 +12,7 @@ import { createReactions } from '../../stores/lib/Reaction'; | |||
12 | import { createActionBindings } from '../utils/ActionBinding'; | 12 | import { createActionBindings } from '../utils/ActionBinding'; |
13 | import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.'; | 13 | import { DEFAULT_TODOS_WIDTH, TODOS_MIN_WIDTH, DEFAULT_TODOS_VISIBLE } from '.'; |
14 | import { IPC } from './constants'; | 14 | import { IPC } from './constants'; |
15 | import { state as delayAppState } from '../delayApp'; | ||
15 | 16 | ||
16 | const debug = require('debug')('Franz:feature:todos:store'); | 17 | const debug = require('debug')('Franz:feature:todos:store'); |
17 | 18 | ||
@@ -29,6 +30,7 @@ export default class TodoStore extends FeatureStore { | |||
29 | } | 30 | } |
30 | 31 | ||
31 | @computed get isTodosPanelVisible() { | 32 | @computed get isTodosPanelVisible() { |
33 | if (this.stores.services.all.length === 0 || delayAppState.isDelayAppScreenVisible) return false; | ||
32 | if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE; | 34 | if (this.settings.isTodosPanelVisible === undefined) return DEFAULT_TODOS_VISIBLE; |
33 | 35 | ||
34 | return this.settings.isTodosPanelVisible; | 36 | return this.settings.isTodosPanelVisible; |
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index 684e50dd0..e7bc0b157 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js | |||
@@ -7,6 +7,7 @@ import { H1, Icon, ProBadge } from '@meetfranz/ui'; | |||
7 | import { Button } from '@meetfranz/forms/lib'; | 7 | import { Button } from '@meetfranz/forms/lib'; |
8 | import ReactTooltip from 'react-tooltip'; | 8 | import ReactTooltip from 'react-tooltip'; |
9 | 9 | ||
10 | import { mdiPlusBox, mdiSettings } from '@mdi/js'; | ||
10 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; | 11 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; |
11 | import { workspaceActions } from '../actions'; | 12 | import { workspaceActions } from '../actions'; |
12 | import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index'; | 13 | import { GA_CATEGORY_WORKSPACES, workspaceStore } from '../index'; |
@@ -159,7 +160,7 @@ class WorkspaceDrawer extends Component { | |||
159 | data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`} | 160 | data-tip={`${intl.formatMessage(messages.workspacesSettingsTooltip)}`} |
160 | > | 161 | > |
161 | <Icon | 162 | <Icon |
162 | icon="mdiSettings" | 163 | icon={mdiSettings} |
163 | size={1.5} | 164 | size={1.5} |
164 | className={classes.workspacesSettingsButtonIcon} | 165 | className={classes.workspacesSettingsButtonIcon} |
165 | /> | 166 | /> |
@@ -184,7 +185,7 @@ class WorkspaceDrawer extends Component { | |||
184 | className={classes.premiumCtaButton} | 185 | className={classes.premiumCtaButton} |
185 | buttonType="primary" | 186 | buttonType="primary" |
186 | label={intl.formatMessage(messages.premiumCtaButtonLabel)} | 187 | label={intl.formatMessage(messages.premiumCtaButtonLabel)} |
187 | icon="mdiPlusBox" | 188 | icon={mdiPlusBox} |
188 | onClick={() => { | 189 | onClick={() => { |
189 | workspaceActions.openWorkspaceSettings(); | 190 | workspaceActions.openWorkspaceSettings(); |
190 | gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta'); | 191 | gaEvent(GA_CATEGORY_WORKSPACES, 'add', 'drawerPremiumCta'); |
@@ -227,7 +228,7 @@ class WorkspaceDrawer extends Component { | |||
227 | }} | 228 | }} |
228 | > | 229 | > |
229 | <Icon | 230 | <Icon |
230 | icon="mdiPlusBox" | 231 | icon={mdiPlusBox} |
231 | size={1} | 232 | size={1} |
232 | className={classes.workspacesSettingsButtonIcon} | 233 | className={classes.workspacesSettingsButtonIcon} |
233 | /> | 234 | /> |
diff --git a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js index c4a800a7b..a70d1d66f 100644 --- a/src/features/workspaces/components/WorkspaceSwitchingIndicator.js +++ b/src/features/workspaces/components/WorkspaceSwitchingIndicator.js | |||
@@ -21,11 +21,8 @@ const styles = theme => ({ | |||
21 | alignItems: 'flex-start', | 21 | alignItems: 'flex-start', |
22 | position: 'absolute', | 22 | position: 'absolute', |
23 | transition: 'width 0.5s ease', | 23 | transition: 'width 0.5s ease', |
24 | width: '100%', | ||
25 | marginTop: '20px', | ||
26 | }, | ||
27 | wrapperWhenDrawerIsOpen: { | ||
28 | width: `calc(100% - ${theme.workspaces.drawer.width}px)`, | 24 | width: `calc(100% - ${theme.workspaces.drawer.width}px)`, |
25 | marginTop: '20px', | ||
29 | }, | 26 | }, |
30 | component: { | 27 | component: { |
31 | background: 'rgba(20, 20, 20, 0.4)', | 28 | background: 'rgba(20, 20, 20, 0.4)', |
@@ -64,14 +61,13 @@ class WorkspaceSwitchingIndicator extends Component { | |||
64 | render() { | 61 | render() { |
65 | const { classes, theme } = this.props; | 62 | const { classes, theme } = this.props; |
66 | const { intl } = this.context; | 63 | const { intl } = this.context; |
67 | const { isSwitchingWorkspace, isWorkspaceDrawerOpen, nextWorkspace } = workspaceStore; | 64 | const { isSwitchingWorkspace, nextWorkspace } = workspaceStore; |
68 | if (!isSwitchingWorkspace) return null; | 65 | if (!isSwitchingWorkspace) return null; |
69 | const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; | 66 | const nextWorkspaceName = nextWorkspace ? nextWorkspace.name : 'All services'; |
70 | return ( | 67 | return ( |
71 | <div | 68 | <div |
72 | className={classnames([ | 69 | className={classnames([ |
73 | classes.wrapper, | 70 | classes.wrapper, |
74 | isWorkspaceDrawerOpen ? classes.wrapperWhenDrawerIsOpen : null, | ||
75 | ])} | 71 | ])} |
76 | > | 72 | > |
77 | <div className={classes.component}> | 73 | <div className={classes.component}> |
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index 09c98ab8c..059a681de 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js | |||
@@ -1,6 +1,6 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | 1 | import React, { Component, Fragment } 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, inject } from 'mobx-react'; |
4 | import { defineMessages, intlShape } from 'react-intl'; | 4 | import { defineMessages, intlShape } from 'react-intl'; |
5 | import injectSheet from 'react-jss'; | 5 | import injectSheet from 'react-jss'; |
6 | import { Infobox } from '@meetfranz/ui'; | 6 | import { Infobox } from '@meetfranz/ui'; |
@@ -12,6 +12,8 @@ import Request from '../../../stores/lib/Request'; | |||
12 | import Appear from '../../../components/ui/effects/Appear'; | 12 | import Appear from '../../../components/ui/effects/Appear'; |
13 | import { workspaceStore } from '../index'; | 13 | import { workspaceStore } from '../index'; |
14 | import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; | 14 | import PremiumFeatureContainer from '../../../components/ui/PremiumFeatureContainer'; |
15 | import UIStore from '../../../stores/UIStore'; | ||
16 | import ActivateTrialButton from '../../../components/ui/ActivateTrialButton'; | ||
15 | 17 | ||
16 | const messages = defineMessages({ | 18 | const messages = defineMessages({ |
17 | headline: { | 19 | headline: { |
@@ -62,17 +64,27 @@ const styles = theme => ({ | |||
62 | height: 'auto', | 64 | height: 'auto', |
63 | }, | 65 | }, |
64 | premiumAnnouncement: { | 66 | premiumAnnouncement: { |
65 | padding: '20px', | 67 | padding: 20, |
66 | backgroundColor: '#3498db', | 68 | // backgroundColor: '#3498db', |
67 | marginLeft: '-20px', | 69 | marginLeft: -20, |
68 | marginBottom: '20px', | 70 | marginBottom: 40, |
71 | paddingBottom: 40, | ||
69 | height: 'auto', | 72 | height: 'auto', |
70 | color: 'white', | 73 | display: 'flex', |
71 | borderRadius: theme.borderRadius, | 74 | borderBottom: [1, 'solid', theme.inputBackground], |
75 | }, | ||
76 | teaserImage: { | ||
77 | width: 200, | ||
78 | height: '100%', | ||
79 | float: 'left', | ||
80 | margin: [-8, 0, 0, -20], | ||
81 | }, | ||
82 | upgradeCTA: { | ||
83 | marginTop: 20, | ||
72 | }, | 84 | }, |
73 | }); | 85 | }); |
74 | 86 | ||
75 | @injectSheet(styles) @observer | 87 | @inject('stores') @injectSheet(styles) @observer |
76 | class WorkspacesDashboard extends Component { | 88 | class WorkspacesDashboard extends Component { |
77 | static propTypes = { | 89 | static propTypes = { |
78 | classes: PropTypes.object.isRequired, | 90 | classes: PropTypes.object.isRequired, |
@@ -100,7 +112,9 @@ class WorkspacesDashboard extends Component { | |||
100 | onWorkspaceClick, | 112 | onWorkspaceClick, |
101 | workspaces, | 113 | workspaces, |
102 | } = this.props; | 114 | } = this.props; |
115 | |||
103 | const { intl } = this.context; | 116 | const { intl } = this.context; |
117 | |||
104 | return ( | 118 | return ( |
105 | <div className="settings__main"> | 119 | <div className="settings__main"> |
106 | <div className="settings__header"> | 120 | <div className="settings__header"> |
@@ -138,13 +152,21 @@ class WorkspacesDashboard extends Component { | |||
138 | 152 | ||
139 | {workspaceStore.isPremiumUpgradeRequired && ( | 153 | {workspaceStore.isPremiumUpgradeRequired && ( |
140 | <div className={classes.premiumAnnouncement}> | 154 | <div className={classes.premiumAnnouncement}> |
141 | <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2> | 155 | <img src={`./assets/images/workspaces/teaser_${this.props.stores.ui.isDarkThemeActive ? 'dark' : 'light'}.png`} className={classes.teaserImage} alt="" /> |
142 | <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p> | 156 | <div> |
157 | <h2>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h2> | ||
158 | <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p> | ||
159 | <ActivateTrialButton | ||
160 | className={classes.upgradeCTA} | ||
161 | gaEventInfo={{ category: 'Workspaces', event: 'upgrade' }} | ||
162 | short | ||
163 | /> | ||
164 | </div> | ||
143 | </div> | 165 | </div> |
144 | )} | 166 | )} |
145 | 167 | ||
146 | <PremiumFeatureContainer | 168 | <PremiumFeatureContainer |
147 | condition={workspaceStore.isPremiumFeature} | 169 | condition={() => workspaceStore.isPremiumUpgradeRequired} |
148 | gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }} | 170 | gaEventInfo={{ category: 'User', event: 'upgrade', label: 'workspaces' }} |
149 | > | 171 | > |
150 | {/* ===== Create workspace form ===== */} | 172 | {/* ===== Create workspace form ===== */} |
@@ -207,3 +229,9 @@ class WorkspacesDashboard extends Component { | |||
207 | } | 229 | } |
208 | 230 | ||
209 | export default WorkspacesDashboard; | 231 | export default WorkspacesDashboard; |
232 | |||
233 | WorkspacesDashboard.wrappedComponent.propTypes = { | ||
234 | stores: PropTypes.shape({ | ||
235 | ui: PropTypes.instanceOf(UIStore).isRequired, | ||
236 | }).isRequired, | ||
237 | }; | ||
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index a82f6895c..4a1f80b4e 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js | |||
@@ -253,11 +253,10 @@ export default class WorkspacesStore extends FeatureStore { | |||
253 | }; | 253 | }; |
254 | 254 | ||
255 | _setIsPremiumFeatureReaction = () => { | 255 | _setIsPremiumFeatureReaction = () => { |
256 | const { features, user } = this.stores; | 256 | const { features } = this.stores; |
257 | const { isPremium } = user.data; | 257 | const { isWorkspaceIncludedInCurrentPlan } = features.features; |
258 | const { isWorkspacePremiumFeature } = features.features; | 258 | this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan; |
259 | this.isPremiumFeature = isWorkspacePremiumFeature; | 259 | this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan; |
260 | this.isPremiumUpgradeRequired = isWorkspacePremiumFeature && !isPremium; | ||
261 | }; | 260 | }; |
262 | 261 | ||
263 | _setWorkspaceBeingEditedReaction = () => { | 262 | _setWorkspaceBeingEditedReaction = () => { |