diff options
author | Vijay A <avijayr@protonmail.com> | 2021-07-17 20:32:22 +0530 |
---|---|---|
committer | Vijay Raghavan Aravamudhan <vraravam@users.noreply.github.com> | 2021-07-20 16:02:15 +0000 |
commit | 45373f655f68fdd0b320cde175b6108454ad4731 (patch) | |
tree | c1ccb0c73639d754b68a36a1977b74471fe4b566 /src/features | |
parent | New Crowdin updates (#1668) (diff) | |
download | ferdium-app-45373f655f68fdd0b320cde175b6108454ad4731.tar.gz ferdium-app-45373f655f68fdd0b320cde175b6108454ad4731.tar.zst ferdium-app-45373f655f68fdd0b320cde175b6108454ad4731.zip |
Removed Franz paid plans features:
- serviceLimit
- planSelection
- trialStatusBar
and other Franz features that were for different tiers of subscription.
Diffstat (limited to 'src/features')
31 files changed, 92 insertions, 1835 deletions
diff --git a/src/features/communityRecipes/index.js b/src/features/communityRecipes/index.js index 39f7e9cd6..828c6d867 100644 --- a/src/features/communityRecipes/index.js +++ b/src/features/communityRecipes/index.js | |||
@@ -1,26 +1,7 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import { CommunityRecipesStore } from './store'; | 1 | import { CommunityRecipesStore } from './store'; |
3 | 2 | ||
4 | const debug = require('debug')('Ferdi:feature:communityRecipes'); | ||
5 | |||
6 | export const communityRecipesStore = new CommunityRecipesStore(); | 3 | export const communityRecipesStore = new CommunityRecipesStore(); |
7 | 4 | ||
8 | export default function initCommunityRecipes(stores, actions) { | 5 | export default function initCommunityRecipes(stores, actions) { |
9 | const { features } = stores; | ||
10 | |||
11 | communityRecipesStore.start(stores, actions); | 6 | communityRecipesStore.start(stores, actions); |
12 | |||
13 | // Toggle communityRecipe premium status | ||
14 | reaction( | ||
15 | () => ( | ||
16 | features.features.isCommunityRecipesIncludedInCurrentPlan | ||
17 | ), | ||
18 | (isPremiumFeature) => { | ||
19 | debug('Community recipes is premium feature: ', isPremiumFeature); | ||
20 | communityRecipesStore.isCommunityRecipesIncludedInCurrentPlan = true; | ||
21 | }, | ||
22 | { | ||
23 | fireImmediately: true, | ||
24 | }, | ||
25 | ); | ||
26 | } | 7 | } |
diff --git a/src/features/communityRecipes/store.js b/src/features/communityRecipes/store.js index 3a60e5449..acc1e52f6 100644 --- a/src/features/communityRecipes/store.js +++ b/src/features/communityRecipes/store.js | |||
@@ -1,11 +1,9 @@ | |||
1 | import { computed, observable } from 'mobx'; | 1 | import { computed } from 'mobx'; |
2 | import { FeatureStore } from '../utils/FeatureStore'; | 2 | import { FeatureStore } from '../utils/FeatureStore'; |
3 | 3 | ||
4 | const debug = require('debug')('Ferdi:feature:communityRecipes:store'); | 4 | const debug = require('debug')('Ferdi:feature:communityRecipes:store'); |
5 | 5 | ||
6 | export class CommunityRecipesStore extends FeatureStore { | 6 | export class CommunityRecipesStore extends FeatureStore { |
7 | @observable isCommunityRecipesIncludedInCurrentPlan = true; | ||
8 | |||
9 | start(stores, actions) { | 7 | start(stores, actions) { |
10 | debug('start'); | 8 | debug('start'); |
11 | this.stores = stores; | 9 | this.stores = stores; |
diff --git a/src/features/delayApp/Component.js b/src/features/delayApp/Component.js deleted file mode 100644 index 6471240ab..000000000 --- a/src/features/delayApp/Component.js +++ /dev/null | |||
@@ -1,120 +0,0 @@ | |||
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 | |||
7 | import { Button } from '@meetfranz/forms'; | ||
8 | |||
9 | import { config } from './constants'; | ||
10 | import styles from './styles'; | ||
11 | import UserStore from '../../stores/UserStore'; | ||
12 | import UIStore from '../../stores/UIStore'; | ||
13 | import { FeatureStore } from '../utils/FeatureStore'; | ||
14 | |||
15 | const messages = defineMessages({ | ||
16 | headline: { | ||
17 | id: 'feature.delayApp.headline', | ||
18 | defaultMessage: '!!!Please purchase license to skip waiting', | ||
19 | }, | ||
20 | headlineTrial: { | ||
21 | id: 'feature.delayApp.trial.headline', | ||
22 | defaultMessage: '!!!Get the free Franz Professional 14 day trial and skip the line', | ||
23 | }, | ||
24 | action: { | ||
25 | id: 'feature.delayApp.upgrade.action', | ||
26 | defaultMessage: '!!!Upgrade Franz', | ||
27 | }, | ||
28 | actionTrial: { | ||
29 | id: 'feature.delayApp.trial.action', | ||
30 | defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional', | ||
31 | }, | ||
32 | text: { | ||
33 | id: 'feature.delayApp.text', | ||
34 | defaultMessage: '!!!Ferdi will continue in {seconds} seconds.', | ||
35 | }, | ||
36 | }); | ||
37 | |||
38 | export default @inject('stores', 'actions') @injectSheet(styles) @observer class DelayApp extends Component { | ||
39 | static propTypes = { | ||
40 | // eslint-disable-next-line | ||
41 | classes: PropTypes.object.isRequired, | ||
42 | }; | ||
43 | |||
44 | static contextTypes = { | ||
45 | intl: intlShape, | ||
46 | }; | ||
47 | |||
48 | state = { | ||
49 | countdown: config.delayDuration, | ||
50 | }; | ||
51 | |||
52 | countdownInterval = null; | ||
53 | |||
54 | countdownIntervalTimeout = 1000; | ||
55 | |||
56 | componentDidMount() { | ||
57 | this.countdownInterval = setInterval(() => { | ||
58 | this.setState(prevState => ({ | ||
59 | countdown: prevState.countdown - this.countdownIntervalTimeout, | ||
60 | })); | ||
61 | |||
62 | if (this.state.countdown <= 0) { | ||
63 | // reload(); | ||
64 | clearInterval(this.countdownInterval); | ||
65 | } | ||
66 | }, this.countdownIntervalTimeout); | ||
67 | } | ||
68 | |||
69 | componentWillUnmount() { | ||
70 | clearInterval(this.countdownInterval); | ||
71 | } | ||
72 | |||
73 | handleCTAClick() { | ||
74 | const { actions, stores } = this.props; | ||
75 | const { hadSubscription } = stores.user.data; | ||
76 | const { defaultTrialPlan } = stores.features.features; | ||
77 | |||
78 | if (!hadSubscription) { | ||
79 | actions.user.activateTrial({ planId: defaultTrialPlan }); | ||
80 | } else { | ||
81 | actions.ui.openSettings({ path: 'user' }); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | render() { | ||
86 | const { classes, stores } = this.props; | ||
87 | const { intl } = this.context; | ||
88 | |||
89 | const { hadSubscription } = stores.user.data; | ||
90 | |||
91 | return ( | ||
92 | <div className={`${classes.container}`}> | ||
93 | <h1 className={classes.headline}>{intl.formatMessage(hadSubscription ? messages.headline : messages.headlineTrial)}</h1> | ||
94 | <Button | ||
95 | label={intl.formatMessage(hadSubscription ? messages.action : messages.actionTrial)} | ||
96 | className={classes.button} | ||
97 | buttonType="inverted" | ||
98 | onClick={this.handleCTAClick.bind(this)} | ||
99 | busy={stores.user.activateTrialRequest.isExecuting} | ||
100 | /> | ||
101 | <p className="footnote"> | ||
102 | {intl.formatMessage(messages.text, { | ||
103 | seconds: this.state.countdown / 1000, | ||
104 | })} | ||
105 | </p> | ||
106 | </div> | ||
107 | ); | ||
108 | } | ||
109 | } | ||
110 | |||
111 | DelayApp.wrappedComponent.propTypes = { | ||
112 | stores: PropTypes.shape({ | ||
113 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
114 | features: PropTypes.instanceOf(FeatureStore).isRequired, | ||
115 | }).isRequired, | ||
116 | actions: PropTypes.shape({ | ||
117 | ui: PropTypes.instanceOf(UIStore).isRequired, | ||
118 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
119 | }).isRequired, | ||
120 | }; | ||
diff --git a/src/features/delayApp/constants.js b/src/features/delayApp/constants.js deleted file mode 100644 index 72cc4246e..000000000 --- a/src/features/delayApp/constants.js +++ /dev/null | |||
@@ -1,6 +0,0 @@ | |||
1 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; | ||
2 | |||
3 | export const config = { | ||
4 | delayOffset: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset, | ||
5 | delayDuration: DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait, | ||
6 | }; | ||
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js deleted file mode 100644 index f0c2bdc82..000000000 --- a/src/features/delayApp/index.js +++ /dev/null | |||
@@ -1,80 +0,0 @@ | |||
1 | import { autorun, observable, reaction } from 'mobx'; | ||
2 | import moment from 'moment'; | ||
3 | import DelayAppComponent from './Component'; | ||
4 | import { config } from './constants'; | ||
5 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; | ||
6 | import { getUserWorkspacesRequest } from '../workspaces/api'; | ||
7 | |||
8 | const debug = require('debug')('Ferdi:feature:delayApp'); | ||
9 | |||
10 | export const state = observable({ | ||
11 | isDelayAppScreenVisible: DEFAULT_FEATURES_CONFIG.needToWaitToProceed, | ||
12 | }); | ||
13 | |||
14 | function setVisibility(value) { | ||
15 | Object.assign(state, { | ||
16 | isDelayAppScreenVisible: value, | ||
17 | }); | ||
18 | } | ||
19 | |||
20 | export default function init(stores) { | ||
21 | debug('Initializing `delayApp` feature'); | ||
22 | |||
23 | let shownAfterLaunch = false; | ||
24 | let timeLastDelay = moment(); | ||
25 | |||
26 | window.ferdi.features.delayApp = { | ||
27 | state, | ||
28 | }; | ||
29 | |||
30 | reaction( | ||
31 | () => ( | ||
32 | stores.user.isLoggedIn | ||
33 | && stores.services.allServicesRequest.wasExecuted | ||
34 | && getUserWorkspacesRequest.wasExecuted | ||
35 | && stores.features.features.needToWaitToProceed | ||
36 | && !stores.user.data.isPremium | ||
37 | ), | ||
38 | (isEnabled) => { | ||
39 | if (isEnabled) { | ||
40 | debug('Enabling `delayApp` feature'); | ||
41 | |||
42 | const { needToWaitToProceedConfig: globalConfig } = stores.features.features; | ||
43 | |||
44 | config.delayOffset = globalConfig.delayOffset !== undefined ? globalConfig.delayOffset : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.delayOffset; | ||
45 | config.delayDuration = globalConfig.wait !== undefined ? globalConfig.wait : DEFAULT_FEATURES_CONFIG.needToWaitToProceedConfig.wait; | ||
46 | |||
47 | autorun(() => { | ||
48 | const { isAnnouncementShown } = stores.announcements; | ||
49 | if (stores.services.allDisplayed.length === 0 || isAnnouncementShown) { | ||
50 | shownAfterLaunch = true; | ||
51 | setVisibility(false); | ||
52 | return; | ||
53 | } | ||
54 | |||
55 | const diff = moment().diff(timeLastDelay); | ||
56 | const itsTimeToWait = diff >= config.delayOffset; | ||
57 | if (!isAnnouncementShown && ((stores.app.isFocused && itsTimeToWait) || !shownAfterLaunch)) { | ||
58 | debug(`App will be delayed for ${config.delayDuration / 1000}s`); | ||
59 | |||
60 | setVisibility(true); | ||
61 | |||
62 | setTimeout(() => { | ||
63 | debug('Resetting app delay'); | ||
64 | |||
65 | shownAfterLaunch = true; | ||
66 | timeLastDelay = moment(); | ||
67 | setVisibility(false); | ||
68 | }, config.delayDuration + 1000); // timer needs to be able to hit 0 | ||
69 | } else { | ||
70 | setVisibility(false); | ||
71 | } | ||
72 | }); | ||
73 | } else { | ||
74 | setVisibility(false); | ||
75 | } | ||
76 | }, | ||
77 | ); | ||
78 | } | ||
79 | |||
80 | export const Component = DelayAppComponent; | ||
diff --git a/src/features/delayApp/styles.js b/src/features/delayApp/styles.js deleted file mode 100644 index 69c3c7a27..000000000 --- a/src/features/delayApp/styles.js +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | export default theme => ({ | ||
2 | container: { | ||
3 | background: theme.colorBackground, | ||
4 | top: 0, | ||
5 | width: '100%', | ||
6 | display: 'flex', | ||
7 | 'flex-direction': 'column', | ||
8 | 'align-items': 'center', | ||
9 | 'justify-content': 'center', | ||
10 | 'z-index': 150, | ||
11 | }, | ||
12 | headline: { | ||
13 | color: theme.colorHeadline, | ||
14 | margin: [25, 0, 40], | ||
15 | 'max-width': 500, | ||
16 | 'text-align': 'center', | ||
17 | 'line-height': '1.3em', | ||
18 | }, | ||
19 | button: { | ||
20 | margin: [40, 0, 20], | ||
21 | }, | ||
22 | }); | ||
diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js deleted file mode 100644 index 83f58bfd7..000000000 --- a/src/features/planSelection/actions.js +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | ||
3 | |||
4 | export const planSelectionActions = createActionsFromDefinitions({ | ||
5 | downgradeAccount: {}, | ||
6 | hideOverlay: {}, | ||
7 | }, PropTypes.checkPropTypes); | ||
8 | |||
9 | export default planSelectionActions; | ||
diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js deleted file mode 100644 index 16bf9ff2d..000000000 --- a/src/features/planSelection/api.js +++ /dev/null | |||
@@ -1,26 +0,0 @@ | |||
1 | import { sendAuthRequest } from '../../api/utils/auth'; | ||
2 | import Request from '../../stores/lib/Request'; | ||
3 | import apiBase from '../../api/apiBase'; | ||
4 | |||
5 | const debug = require('debug')('Ferdi:feature:planSelection:api'); | ||
6 | |||
7 | export const planSelectionApi = { | ||
8 | downgrade: async () => { | ||
9 | const url = `${apiBase()}/payment/downgrade`; | ||
10 | const options = { | ||
11 | method: 'PUT', | ||
12 | }; | ||
13 | debug('downgrade UPDATE', url, options); | ||
14 | const result = await sendAuthRequest(url, options); | ||
15 | debug('downgrade RESULT', result); | ||
16 | if (!result.ok) throw result; | ||
17 | |||
18 | return result.ok; | ||
19 | }, | ||
20 | }; | ||
21 | |||
22 | export const downgradeUserRequest = new Request(planSelectionApi, 'downgrade'); | ||
23 | |||
24 | export const resetApiRequests = () => { | ||
25 | downgradeUserRequest.reset(); | ||
26 | }; | ||
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js deleted file mode 100644 index e90532dec..000000000 --- a/src/features/planSelection/components/PlanItem.js +++ /dev/null | |||
@@ -1,215 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { defineMessages, intlShape } from 'react-intl'; | ||
5 | import injectSheet from 'react-jss'; | ||
6 | import classnames from 'classnames'; | ||
7 | import color from 'color'; | ||
8 | |||
9 | import { H2 } from '@meetfranz/ui'; | ||
10 | |||
11 | import { Button } from '@meetfranz/forms'; | ||
12 | import { mdiArrowRight } from '@mdi/js'; | ||
13 | |||
14 | const messages = defineMessages({ | ||
15 | perMonth: { | ||
16 | id: 'subscription.interval.perMonth', | ||
17 | defaultMessage: '!!!per month', | ||
18 | }, | ||
19 | perMonthPerUser: { | ||
20 | id: 'subscription.interval.perMonthPerUser', | ||
21 | defaultMessage: '!!!per month & user', | ||
22 | }, | ||
23 | bestValue: { | ||
24 | id: 'subscription.bestValue', | ||
25 | defaultMessage: '!!!Best value', | ||
26 | }, | ||
27 | }); | ||
28 | |||
29 | const 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: 10, | ||
42 | fontSize: 30, | ||
43 | color: theme.styleTypes.primary.contrast, | ||
44 | }, | ||
45 | }, | ||
46 | currency: { | ||
47 | fontSize: 35, | ||
48 | }, | ||
49 | priceWrapper: { | ||
50 | height: 50, | ||
51 | marginBottom: 0, | ||
52 | marginTop: ({ text }) => (!text ? 15 : 0), | ||
53 | }, | ||
54 | price: { | ||
55 | fontSize: 50, | ||
56 | |||
57 | '& sup': { | ||
58 | fontSize: 20, | ||
59 | verticalAlign: 20, | ||
60 | }, | ||
61 | }, | ||
62 | text: { | ||
63 | marginBottom: 'auto', | ||
64 | }, | ||
65 | cta: { | ||
66 | background: theme.styleTypes.primary.accent, | ||
67 | color: theme.styleTypes.primary.contrast, | ||
68 | margin: [30, 'auto', 0, 'auto'], | ||
69 | }, | ||
70 | divider: { | ||
71 | width: 40, | ||
72 | border: 0, | ||
73 | borderTop: [1, 'solid', theme.styleTypes.primary.contrast], | ||
74 | margin: [15, 'auto', 20], | ||
75 | }, | ||
76 | header: { | ||
77 | padding: 20, | ||
78 | background: color(theme.styleTypes.primary.accent).darken(0.25).hex(), | ||
79 | color: theme.styleTypes.primary.contrast, | ||
80 | position: 'relative', | ||
81 | height: 'auto', | ||
82 | }, | ||
83 | content: { | ||
84 | padding: [10, 20, 20], | ||
85 | background: '#EFEFEF', | ||
86 | display: 'flex', | ||
87 | flexDirection: 'column', | ||
88 | justifyContent: 'space-between', | ||
89 | }, | ||
90 | simpleCTA: { | ||
91 | background: 'none', | ||
92 | color: theme.styleTypes.primary.accent, | ||
93 | |||
94 | '& svg': { | ||
95 | fill: theme.styleTypes.primary.accent, | ||
96 | }, | ||
97 | }, | ||
98 | bestValue: { | ||
99 | background: theme.styleTypes.success.accent, | ||
100 | color: theme.styleTypes.success.contrast, | ||
101 | right: -66, | ||
102 | top: -40, | ||
103 | height: 'auto', | ||
104 | position: 'absolute', | ||
105 | transform: 'rotateZ(45deg)', | ||
106 | textAlign: 'center', | ||
107 | padding: [5, 50], | ||
108 | transformOrigin: 'left bottom', | ||
109 | fontSize: 12, | ||
110 | boxShadow: '0 2px 6px rgba(0,0,0,0.15)', | ||
111 | }, | ||
112 | }); | ||
113 | |||
114 | export default @observer @injectSheet(styles) class PlanItem extends Component { | ||
115 | static propTypes = { | ||
116 | name: PropTypes.string.isRequired, | ||
117 | text: PropTypes.string.isRequired, | ||
118 | price: PropTypes.number.isRequired, | ||
119 | currency: PropTypes.string.isRequired, | ||
120 | upgrade: PropTypes.func.isRequired, | ||
121 | ctaLabel: PropTypes.string.isRequired, | ||
122 | simpleCTA: PropTypes.bool, | ||
123 | perUser: PropTypes.bool, | ||
124 | classes: PropTypes.object.isRequired, | ||
125 | bestValue: PropTypes.bool, | ||
126 | className: PropTypes.string, | ||
127 | children: PropTypes.element, | ||
128 | }; | ||
129 | |||
130 | static defaultProps = { | ||
131 | simpleCTA: false, | ||
132 | perUser: false, | ||
133 | children: null, | ||
134 | bestValue: false, | ||
135 | className: '', | ||
136 | } | ||
137 | |||
138 | static contextTypes = { | ||
139 | intl: intlShape, | ||
140 | }; | ||
141 | |||
142 | render() { | ||
143 | const { | ||
144 | name, | ||
145 | text, | ||
146 | price, | ||
147 | currency, | ||
148 | classes, | ||
149 | upgrade, | ||
150 | ctaLabel, | ||
151 | simpleCTA, | ||
152 | perUser, | ||
153 | bestValue, | ||
154 | className, | ||
155 | children, | ||
156 | } = this.props; | ||
157 | const { intl } = this.context; | ||
158 | |||
159 | const priceParts = `${price}`.split('.'); | ||
160 | |||
161 | return ( | ||
162 | <div className={classnames({ | ||
163 | [classes.root]: true, | ||
164 | [className]: className, | ||
165 | })} | ||
166 | > | ||
167 | <div className={classes.header}> | ||
168 | {bestValue && ( | ||
169 | <div className={classes.bestValue}> | ||
170 | {intl.formatMessage(messages.bestValue)} | ||
171 | </div> | ||
172 | )} | ||
173 | <H2 className={classes.planName}>{name}</H2> | ||
174 | {text && ( | ||
175 | <> | ||
176 | <p className={classes.text}> | ||
177 | {text} | ||
178 | </p> | ||
179 | <hr className={classes.divider} /> | ||
180 | </> | ||
181 | )} | ||
182 | <p className={classes.priceWrapper}> | ||
183 | <span className={classes.currency}>{currency}</span> | ||
184 | <span className={classes.price}> | ||
185 | {priceParts[0]} | ||
186 | <sup>{priceParts[1]}</sup> | ||
187 | </span> | ||
188 | </p> | ||
189 | <p className={classes.interval}> | ||
190 | {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)} | ||
191 | </p> | ||
192 | </div> | ||
193 | |||
194 | <div className={classes.content}> | ||
195 | {children} | ||
196 | |||
197 | <Button | ||
198 | className={classnames({ | ||
199 | [classes.cta]: true, | ||
200 | [classes.simpleCTA]: simpleCTA, | ||
201 | })} | ||
202 | icon={simpleCTA ? mdiArrowRight : null} | ||
203 | label={( | ||
204 | <> | ||
205 | {ctaLabel} | ||
206 | </> | ||
207 | )} | ||
208 | onClick={upgrade} | ||
209 | /> | ||
210 | </div> | ||
211 | |||
212 | </div> | ||
213 | ); | ||
214 | } | ||
215 | } | ||
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js deleted file mode 100644 index 819a9df5b..000000000 --- a/src/features/planSelection/components/PlanSelection.js +++ /dev/null | |||
@@ -1,269 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import { H1, H2, Icon } from '@meetfranz/ui'; | ||
7 | import color from 'color'; | ||
8 | |||
9 | import { mdiArrowRight } from '@mdi/js'; | ||
10 | import PlanItem from './PlanItem'; | ||
11 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
12 | import { DEV_API_FRANZ_WEBSITE, PLANS } from '../../../config'; | ||
13 | import { FeatureList } from '../../../components/ui/FeatureList'; | ||
14 | import Appear from '../../../components/ui/effects/Appear'; | ||
15 | |||
16 | const messages = defineMessages({ | ||
17 | welcome: { | ||
18 | id: 'feature.planSelection.fullscreen.welcome', | ||
19 | defaultMessage: '!!!Are you ready to choose, {name}', | ||
20 | }, | ||
21 | subheadline: { | ||
22 | id: 'feature.planSelection.fullscreen.subheadline', | ||
23 | 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.', | ||
24 | }, | ||
25 | textFree: { | ||
26 | id: 'feature.planSelection.free.text', | ||
27 | defaultMessage: '!!!Basic functionality', | ||
28 | }, | ||
29 | textPersonal: { | ||
30 | id: 'feature.planSelection.personal.text', | ||
31 | defaultMessage: '!!!More services, no waiting - ideal for personal use.', | ||
32 | }, | ||
33 | textProfessional: { | ||
34 | id: 'feature.planSelection.pro.text', | ||
35 | defaultMessage: '!!!Unlimited services and professional features for you - and your team.', | ||
36 | }, | ||
37 | ctaStayOnFree: { | ||
38 | id: 'feature.planSelection.cta.stayOnFree', | ||
39 | defaultMessage: '!!!Stay on Free', | ||
40 | }, | ||
41 | ctaDowngradeFree: { | ||
42 | id: 'feature.planSelection.cta.ctaDowngradeFree', | ||
43 | defaultMessage: '!!!Downgrade to Free', | ||
44 | }, | ||
45 | actionTrial: { | ||
46 | id: 'feature.planSelection.cta.trial', | ||
47 | defaultMessage: '!!!Start my free 14-days Trial', | ||
48 | }, | ||
49 | shortActionPersonal: { | ||
50 | id: 'feature.planSelection.cta.upgradePersonal', | ||
51 | defaultMessage: '!!!Choose Personal', | ||
52 | }, | ||
53 | shortActionPro: { | ||
54 | id: 'feature.planSelection.cta.upgradePro', | ||
55 | defaultMessage: '!!!Choose Professional', | ||
56 | }, | ||
57 | fullFeatureList: { | ||
58 | id: 'feature.planSelection.fullFeatureList', | ||
59 | defaultMessage: '!!!Complete comparison of all plans', | ||
60 | }, | ||
61 | pricesBasedOnAnnualPayment: { | ||
62 | id: 'feature.planSelection.pricesBasedOnAnnualPayment', | ||
63 | defaultMessage: '!!!All prices based on yearly payment', | ||
64 | }, | ||
65 | }); | ||
66 | |||
67 | const styles = theme => ({ | ||
68 | root: { | ||
69 | background: theme.colorModalOverlayBackground, | ||
70 | width: '100%', | ||
71 | height: '100%', | ||
72 | position: 'absolute', | ||
73 | top: 0, | ||
74 | left: 0, | ||
75 | display: 'flex', | ||
76 | justifyContent: 'center', | ||
77 | alignItems: 'center', | ||
78 | zIndex: 999999, | ||
79 | overflowY: 'scroll', | ||
80 | }, | ||
81 | container: { | ||
82 | // width: '80%', | ||
83 | height: 'auto', | ||
84 | // background: theme.styleTypes.primary.accent, | ||
85 | // padding: 40, | ||
86 | borderRadius: theme.borderRadius, | ||
87 | maxWidth: 1000, | ||
88 | |||
89 | '& h1, & h2': { | ||
90 | textAlign: 'center', | ||
91 | color: theme.styleTypes.primary.contrast, | ||
92 | }, | ||
93 | }, | ||
94 | plans: { | ||
95 | display: 'flex', | ||
96 | margin: [40, 0, 0], | ||
97 | height: 'auto', | ||
98 | |||
99 | '& > div': { | ||
100 | margin: [0, 15], | ||
101 | flex: 1, | ||
102 | height: 'auto', | ||
103 | background: theme.styleTypes.primary.contrast, | ||
104 | boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()], | ||
105 | }, | ||
106 | }, | ||
107 | headline: { | ||
108 | fontSize: 40, | ||
109 | }, | ||
110 | subheadline: { | ||
111 | maxWidth: 660, | ||
112 | fontSize: 22, | ||
113 | lineHeight: 1.1, | ||
114 | margin: [0, 'auto'], | ||
115 | }, | ||
116 | featureList: { | ||
117 | '& li': { | ||
118 | borderBottom: [1, 'solid', '#CECECE'], | ||
119 | }, | ||
120 | }, | ||
121 | footer: { | ||
122 | display: 'flex', | ||
123 | color: theme.styleTypes.primary.contrast, | ||
124 | marginTop: 20, | ||
125 | padding: [0, 15], | ||
126 | }, | ||
127 | fullFeatureList: { | ||
128 | marginRight: 'auto', | ||
129 | textAlign: 'center', | ||
130 | display: 'flex', | ||
131 | justifyContent: 'center', | ||
132 | alignItems: 'center', | ||
133 | color: `${theme.styleTypes.primary.contrast} !important`, | ||
134 | |||
135 | '& svg': { | ||
136 | marginRight: 5, | ||
137 | }, | ||
138 | }, | ||
139 | scrollContainer: { | ||
140 | border: '1px solid red', | ||
141 | overflow: 'scroll-x', | ||
142 | }, | ||
143 | featuredPlan: { | ||
144 | transform: ({ isPersonalPlanAvailable }) => (isPersonalPlanAvailable ? 'scale(1.05)' : null), | ||
145 | }, | ||
146 | disclaimer: { | ||
147 | textAlign: 'right', | ||
148 | margin: [10, 15, 0, 0], | ||
149 | }, | ||
150 | }); | ||
151 | |||
152 | @injectSheet(styles) @observer | ||
153 | class PlanSelection extends Component { | ||
154 | static propTypes = { | ||
155 | classes: PropTypes.object.isRequired, | ||
156 | firstname: PropTypes.string.isRequired, | ||
157 | plans: PropTypes.object.isRequired, | ||
158 | currency: PropTypes.string.isRequired, | ||
159 | subscriptionExpired: PropTypes.bool.isRequired, | ||
160 | upgradeAccount: PropTypes.func.isRequired, | ||
161 | stayOnFree: PropTypes.func.isRequired, | ||
162 | hadSubscription: PropTypes.bool.isRequired, | ||
163 | isPersonalPlanAvailable: PropTypes.bool, | ||
164 | }; | ||
165 | |||
166 | static defaultProps = { | ||
167 | isPersonalPlanAvailable: true, | ||
168 | } | ||
169 | |||
170 | static contextTypes = { | ||
171 | intl: intlShape, | ||
172 | }; | ||
173 | |||
174 | componentDidMount() { | ||
175 | } | ||
176 | |||
177 | render() { | ||
178 | const { | ||
179 | classes, | ||
180 | firstname, | ||
181 | plans, | ||
182 | currency, | ||
183 | subscriptionExpired, | ||
184 | upgradeAccount, | ||
185 | stayOnFree, | ||
186 | hadSubscription, | ||
187 | isPersonalPlanAvailable, | ||
188 | } = this.props; | ||
189 | |||
190 | const { intl } = this.context; | ||
191 | |||
192 | return ( | ||
193 | <Appear> | ||
194 | <div | ||
195 | className={classes.root} | ||
196 | > | ||
197 | <div className={classes.container}> | ||
198 | <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1> | ||
199 | {isPersonalPlanAvailable && ( | ||
200 | <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2> | ||
201 | )} | ||
202 | <div className={classes.plans}> | ||
203 | <PlanItem | ||
204 | name={i18nPlanName(PLANS.FREE, intl)} | ||
205 | text={isPersonalPlanAvailable ? intl.formatMessage(messages.textFree) : null} | ||
206 | price={0} | ||
207 | currency={currency} | ||
208 | ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)} | ||
209 | upgrade={() => stayOnFree()} | ||
210 | simpleCTA | ||
211 | > | ||
212 | <FeatureList | ||
213 | plan={PLANS.FREE} | ||
214 | className={classes.featureList} | ||
215 | /> | ||
216 | </PlanItem> | ||
217 | <PlanItem | ||
218 | name={i18nPlanName(plans.pro.yearly.id, intl)} | ||
219 | text={isPersonalPlanAvailable ? intl.formatMessage(messages.textProfessional) : null} | ||
220 | price={plans.pro.yearly.price} | ||
221 | currency={currency} | ||
222 | ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)} | ||
223 | upgrade={() => upgradeAccount(plans.pro.yearly.id)} | ||
224 | className={classes.featuredPlan} | ||
225 | perUser | ||
226 | bestValue={isPersonalPlanAvailable} | ||
227 | > | ||
228 | <FeatureList | ||
229 | plan={isPersonalPlanAvailable ? PLANS.PRO : null} | ||
230 | className={classes.featureList} | ||
231 | /> | ||
232 | </PlanItem> | ||
233 | {isPersonalPlanAvailable && ( | ||
234 | <PlanItem | ||
235 | name={i18nPlanName(plans.personal.yearly.id, intl)} | ||
236 | text={intl.formatMessage(messages.textPersonal)} | ||
237 | price={plans.personal.yearly.price} | ||
238 | currency={currency} | ||
239 | ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)} | ||
240 | upgrade={() => upgradeAccount(plans.personal.yearly.id)} | ||
241 | > | ||
242 | <FeatureList | ||
243 | plan={PLANS.PERSONAL} | ||
244 | className={classes.featureList} | ||
245 | /> | ||
246 | </PlanItem> | ||
247 | )} | ||
248 | </div> | ||
249 | <div className={classes.footer}> | ||
250 | <a | ||
251 | href={`${DEV_API_FRANZ_WEBSITE}/pricing`} | ||
252 | target="_blank" | ||
253 | className={classes.fullFeatureList} | ||
254 | > | ||
255 | <Icon icon={mdiArrowRight} /> | ||
256 | {intl.formatMessage(messages.fullFeatureList)} | ||
257 | </a> | ||
258 | {/* <p className={classes.disclaimer}> */} | ||
259 | {intl.formatMessage(messages.pricesBasedOnAnnualPayment)} | ||
260 | {/* </p> */} | ||
261 | </div> | ||
262 | </div> | ||
263 | </div> | ||
264 | </Appear> | ||
265 | ); | ||
266 | } | ||
267 | } | ||
268 | |||
269 | export default PlanSelection; | ||
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js deleted file mode 100644 index 594829c01..000000000 --- a/src/features/planSelection/containers/PlanSelectionScreen.js +++ /dev/null | |||
@@ -1,120 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import { dialog, app } from '@electron/remote'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | |||
7 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
8 | import UserStore from '../../../stores/UserStore'; | ||
9 | import PlanSelection from '../components/PlanSelection'; | ||
10 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | ||
11 | import { planSelectionStore } from '..'; | ||
12 | import PaymentStore from '../../../stores/PaymentStore'; | ||
13 | |||
14 | const messages = defineMessages({ | ||
15 | dialogTitle: { | ||
16 | id: 'feature.planSelection.fullscreen.dialog.title', | ||
17 | defaultMessage: '!!!Downgrade your Franz Plan', | ||
18 | }, | ||
19 | dialogMessage: { | ||
20 | id: 'feature.planSelection.fullscreen.dialog.message', | ||
21 | defaultMessage: '!!!You\'re about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.', | ||
22 | }, | ||
23 | dialogCTADowngrade: { | ||
24 | id: 'feature.planSelection.fullscreen.dialog.cta.downgrade', | ||
25 | defaultMessage: '!!!Downgrade to Free', | ||
26 | }, | ||
27 | dialogCTAUpgrade: { | ||
28 | id: 'feature.planSelection.fullscreen.dialog.cta.upgrade', | ||
29 | defaultMessage: '!!!Choose Personal', | ||
30 | }, | ||
31 | }); | ||
32 | |||
33 | @inject('stores', 'actions') @observer | ||
34 | class PlanSelectionScreen extends Component { | ||
35 | static contextTypes = { | ||
36 | intl: intlShape, | ||
37 | }; | ||
38 | |||
39 | upgradeAccount(planId) { | ||
40 | const { upgradeAccount } = this.props.actions.payment; | ||
41 | |||
42 | upgradeAccount({ | ||
43 | planId, | ||
44 | }); | ||
45 | } | ||
46 | |||
47 | render() { | ||
48 | if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) { | ||
49 | return null; | ||
50 | } | ||
51 | |||
52 | const { intl } = this.context; | ||
53 | |||
54 | const { user, features } = this.props.stores; | ||
55 | const { isPersonalPlanAvailable, pricingConfig } = features.features; | ||
56 | const { plans, currency } = pricingConfig; | ||
57 | const { activateTrial } = this.props.actions.user; | ||
58 | const { downgradeAccount, hideOverlay } = this.props.actions.planSelection; | ||
59 | |||
60 | return ( | ||
61 | <ErrorBoundary> | ||
62 | <PlanSelection | ||
63 | firstname={user.data.firstname} | ||
64 | plans={plans} | ||
65 | currency={currency} | ||
66 | upgradeAccount={(planId) => { | ||
67 | if (user.data.hadSubscription) { | ||
68 | this.upgradeAccount(planId); | ||
69 | } else { | ||
70 | activateTrial({ | ||
71 | planId, | ||
72 | }); | ||
73 | } | ||
74 | }} | ||
75 | stayOnFree={() => { | ||
76 | const selection = dialog.showMessageBoxSync(app.mainWindow, { | ||
77 | type: 'question', | ||
78 | message: intl.formatMessage(messages.dialogTitle), | ||
79 | detail: intl.formatMessage(messages.dialogMessage, { | ||
80 | currency, | ||
81 | price: plans.personal.yearly.price, | ||
82 | }), | ||
83 | buttons: [ | ||
84 | intl.formatMessage(messages.dialogCTADowngrade), | ||
85 | intl.formatMessage(messages.dialogCTAUpgrade), | ||
86 | ], | ||
87 | }); | ||
88 | |||
89 | if (selection === 0) { | ||
90 | downgradeAccount(); | ||
91 | hideOverlay(); | ||
92 | } else { | ||
93 | this.upgradeAccount(plans.personal.yearly.id); | ||
94 | } | ||
95 | }} | ||
96 | subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded} | ||
97 | hadSubscription={user.data.hadSubscription} | ||
98 | isPersonalPlanAvailable={isPersonalPlanAvailable} | ||
99 | /> | ||
100 | </ErrorBoundary> | ||
101 | ); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | export default PlanSelectionScreen; | ||
106 | |||
107 | PlanSelectionScreen.wrappedComponent.propTypes = { | ||
108 | stores: PropTypes.shape({ | ||
109 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
110 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
111 | }).isRequired, | ||
112 | actions: PropTypes.shape({ | ||
113 | payment: PropTypes.instanceOf(PaymentStore), | ||
114 | planSelection: PropTypes.shape({ | ||
115 | downgradeAccount: PropTypes.func.isRequired, | ||
116 | hideOverlay: PropTypes.func.isRequired, | ||
117 | }), | ||
118 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
119 | }).isRequired, | ||
120 | }; | ||
diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js deleted file mode 100644 index b96ad6d8f..000000000 --- a/src/features/planSelection/index.js +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import PlanSelectionStore from './store'; | ||
3 | |||
4 | const debug = require('debug')('Ferdi:feature:planSelection'); | ||
5 | |||
6 | export const planSelectionStore = new PlanSelectionStore(); | ||
7 | |||
8 | export default function initPlanSelection(stores, actions) { | ||
9 | stores.planSelection = planSelectionStore; | ||
10 | const { features } = stores; | ||
11 | |||
12 | // Toggle planSelection feature | ||
13 | reaction( | ||
14 | () => features.features.isPlanSelectionEnabled, | ||
15 | (isEnabled) => { | ||
16 | if (isEnabled) { | ||
17 | debug('Initializing `planSelection` feature'); | ||
18 | planSelectionStore.start(stores, actions); | ||
19 | } else if (planSelectionStore.isFeatureActive) { | ||
20 | debug('Disabling `planSelection` feature'); | ||
21 | planSelectionStore.stop(); | ||
22 | } | ||
23 | }, | ||
24 | { | ||
25 | fireImmediately: true, | ||
26 | }, | ||
27 | ); | ||
28 | } | ||
diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js deleted file mode 100644 index de8fc7584..000000000 --- a/src/features/planSelection/store.js +++ /dev/null | |||
@@ -1,68 +0,0 @@ | |||
1 | import { | ||
2 | action, | ||
3 | observable, | ||
4 | computed, | ||
5 | } from 'mobx'; | ||
6 | |||
7 | import { planSelectionActions } from './actions'; | ||
8 | import { FeatureStore } from '../utils/FeatureStore'; | ||
9 | import { createActionBindings } from '../utils/ActionBinding'; | ||
10 | import { downgradeUserRequest } from './api'; | ||
11 | |||
12 | const debug = require('debug')('Ferdi:feature:planSelection:store'); | ||
13 | |||
14 | export default class PlanSelectionStore extends FeatureStore { | ||
15 | @observable isFeatureEnabled = false; | ||
16 | |||
17 | @observable isFeatureActive = false; | ||
18 | |||
19 | @observable hideOverlay = false; | ||
20 | |||
21 | @computed get showPlanSelectionOverlay() { | ||
22 | const { team, isPremium } = this.stores.user; | ||
23 | if (team && !this.hideOverlay && !isPremium) { | ||
24 | return team.state === 'expired' && !team.userHasDowngraded; | ||
25 | } | ||
26 | |||
27 | return false; | ||
28 | } | ||
29 | |||
30 | // ========== PUBLIC API ========= // | ||
31 | |||
32 | @action start(stores, actions, api) { | ||
33 | debug('PlanSelectionStore::start'); | ||
34 | this.stores = stores; | ||
35 | this.actions = actions; | ||
36 | this.api = api; | ||
37 | |||
38 | // ACTIONS | ||
39 | |||
40 | this._registerActions(createActionBindings([ | ||
41 | [planSelectionActions.downgradeAccount, this._downgradeAccount], | ||
42 | [planSelectionActions.hideOverlay, this._hideOverlay], | ||
43 | ])); | ||
44 | |||
45 | this.isFeatureActive = true; | ||
46 | } | ||
47 | |||
48 | @action stop() { | ||
49 | super.stop(); | ||
50 | debug('PlanSelectionStore::stop'); | ||
51 | this.isFeatureActive = false; | ||
52 | } | ||
53 | |||
54 | // ========== PRIVATE METHODS ========= // | ||
55 | |||
56 | // Actions | ||
57 | @action _downgradeAccount = () => { | ||
58 | downgradeUserRequest.execute(); | ||
59 | } | ||
60 | |||
61 | @action _hideOverlay = () => { | ||
62 | this.hideOverlay = true; | ||
63 | } | ||
64 | |||
65 | @action _showOverlay = () => { | ||
66 | this.hideOverlay = false; | ||
67 | } | ||
68 | } | ||
diff --git a/src/features/serviceLimit/components/LimitReachedInfobox.js b/src/features/serviceLimit/components/LimitReachedInfobox.js deleted file mode 100644 index 424c92990..000000000 --- a/src/features/serviceLimit/components/LimitReachedInfobox.js +++ /dev/null | |||
@@ -1,75 +0,0 @@ | |||
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 | const messages = defineMessages({ | ||
9 | limitReached: { | ||
10 | id: 'feature.serviceLimit.limitReached', | ||
11 | defaultMessage: '!!!You have added {amount} of {limit} services. Please upgrade your account to add more services.', | ||
12 | }, | ||
13 | action: { | ||
14 | id: 'premiumFeature.button.upgradeAccount', | ||
15 | defaultMessage: '!!!Upgrade account', | ||
16 | }, | ||
17 | }); | ||
18 | |||
19 | const styles = theme => ({ | ||
20 | container: { | ||
21 | height: 'auto', | ||
22 | background: theme.styleTypes.warning.accent, | ||
23 | color: theme.styleTypes.warning.contrast, | ||
24 | borderRadius: 0, | ||
25 | marginBottom: 0, | ||
26 | |||
27 | '& > div': { | ||
28 | marginBottom: 0, | ||
29 | }, | ||
30 | |||
31 | '& button': { | ||
32 | color: theme.styleTypes.primary.contrast, | ||
33 | }, | ||
34 | }, | ||
35 | }); | ||
36 | |||
37 | @inject('stores', 'actions') @injectSheet(styles) @observer | ||
38 | class LimitReachedInfobox extends Component { | ||
39 | static propTypes = { | ||
40 | classes: PropTypes.object.isRequired, | ||
41 | stores: PropTypes.object.isRequired, | ||
42 | actions: PropTypes.object.isRequired, | ||
43 | }; | ||
44 | |||
45 | static contextTypes = { | ||
46 | intl: intlShape, | ||
47 | }; | ||
48 | |||
49 | render() { | ||
50 | const { classes, stores, actions } = this.props; | ||
51 | const { intl } = this.context; | ||
52 | |||
53 | const { | ||
54 | serviceLimit, | ||
55 | } = stores; | ||
56 | |||
57 | if (!serviceLimit.userHasReachedServiceLimit) return null; | ||
58 | |||
59 | return ( | ||
60 | <Infobox | ||
61 | icon="mdiInformation" | ||
62 | type="warning" | ||
63 | className={classes.container} | ||
64 | ctaLabel={intl.formatMessage(messages.action)} | ||
65 | ctaOnClick={() => { | ||
66 | actions.ui.openSettings({ path: 'user' }); | ||
67 | }} | ||
68 | > | ||
69 | {intl.formatMessage(messages.limitReached, { amount: serviceLimit.serviceCount, limit: serviceLimit.serviceLimit })} | ||
70 | </Infobox> | ||
71 | ); | ||
72 | } | ||
73 | } | ||
74 | |||
75 | export default LimitReachedInfobox; | ||
diff --git a/src/features/serviceLimit/index.js b/src/features/serviceLimit/index.js deleted file mode 100644 index f867e3d87..000000000 --- a/src/features/serviceLimit/index.js +++ /dev/null | |||
@@ -1,31 +0,0 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import { ServiceLimitStore } from './store'; | ||
3 | |||
4 | const debug = require('debug')('Ferdi:feature:serviceLimit'); | ||
5 | |||
6 | let store = null; | ||
7 | |||
8 | export const serviceLimitStore = new ServiceLimitStore(); | ||
9 | |||
10 | export default function initServiceLimit(stores, actions) { | ||
11 | const { features } = stores; | ||
12 | |||
13 | // Toggle serviceLimit feature | ||
14 | reaction( | ||
15 | () => ( | ||
16 | features.features.isServiceLimitEnabled | ||
17 | ), | ||
18 | (isEnabled) => { | ||
19 | if (isEnabled) { | ||
20 | debug('Initializing `serviceLimit` feature'); | ||
21 | store = serviceLimitStore.start(stores, actions); | ||
22 | } else if (store) { | ||
23 | debug('Disabling `serviceLimit` feature'); | ||
24 | serviceLimitStore.stop(); | ||
25 | } | ||
26 | }, | ||
27 | { | ||
28 | fireImmediately: true, | ||
29 | }, | ||
30 | ); | ||
31 | } | ||
diff --git a/src/features/serviceLimit/store.js b/src/features/serviceLimit/store.js deleted file mode 100644 index b1e55a1fc..000000000 --- a/src/features/serviceLimit/store.js +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | import { computed, observable } from 'mobx'; | ||
2 | import { FeatureStore } from '../utils/FeatureStore'; | ||
3 | import { DEFAULT_SERVICE_LIMIT } from '../../config'; | ||
4 | |||
5 | const debug = require('debug')('Ferdi: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 = false; | ||
16 | } | ||
17 | |||
18 | stop() { | ||
19 | super.stop(); | ||
20 | |||
21 | this.isServiceLimitEnabled = false; | ||
22 | } | ||
23 | |||
24 | @computed get userHasReachedServiceLimit() { | ||
25 | return false; | ||
26 | // if (!this.isServiceLimitEnabled) return false; | ||
27 | |||
28 | // return this.serviceLimit !== 0 && this.serviceCount >= this.serviceLimit; | ||
29 | } | ||
30 | |||
31 | @computed get serviceLimit() { | ||
32 | if (!this.isServiceLimitEnabled || this.stores.features.features.serviceLimitCount === 0) return 0; | ||
33 | |||
34 | return this.stores.features.features.serviceLimitCount || DEFAULT_SERVICE_LIMIT; | ||
35 | } | ||
36 | |||
37 | @computed get serviceCount() { | ||
38 | return this.stores.services.all.length; | ||
39 | } | ||
40 | } | ||
41 | |||
42 | export default ServiceLimitStore; | ||
diff --git a/src/features/serviceProxy/index.js b/src/features/serviceProxy/index.js index f74f5f0b2..eb7116651 100644 --- a/src/features/serviceProxy/index.js +++ b/src/features/serviceProxy/index.js | |||
@@ -5,7 +5,6 @@ const debug = require('debug')('Ferdi:feature:serviceProxy'); | |||
5 | 5 | ||
6 | export const config = observable({ | 6 | export const config = observable({ |
7 | isEnabled: true, | 7 | isEnabled: true, |
8 | isPremium: true, | ||
9 | }); | 8 | }); |
10 | 9 | ||
11 | export default function init(stores) { | 10 | export default function init(stores) { |
@@ -13,7 +12,6 @@ export default function init(stores) { | |||
13 | 12 | ||
14 | autorun(() => { | 13 | autorun(() => { |
15 | config.isEnabled = true; | 14 | config.isEnabled = true; |
16 | config.isIncludedInCurrentPlan = true; | ||
17 | 15 | ||
18 | const services = stores.services.enabled; | 16 | const services = stores.services.enabled; |
19 | const proxySettings = stores.settings.proxy; | 17 | const proxySettings = stores.settings.proxy; |
diff --git a/src/features/shareFranz/Component.js b/src/features/shareFranz/Component.js index f7f8dc41c..b66375453 100644 --- a/src/features/shareFranz/Component.js +++ b/src/features/shareFranz/Component.js | |||
@@ -36,11 +36,11 @@ const messages = defineMessages({ | |||
36 | }, | 36 | }, |
37 | shareTextEmail: { | 37 | shareTextEmail: { |
38 | id: 'feature.shareFranz.shareText.email', | 38 | id: 'feature.shareFranz.shareText.email', |
39 | defaultMessage: '!!! I\'ve added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com', | 39 | defaultMessage: '!!! I\'ve added {count} services to Ferdi! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com', |
40 | }, | 40 | }, |
41 | shareTextTwitter: { | 41 | shareTextTwitter: { |
42 | id: 'feature.shareFranz.shareText.twitter', | 42 | id: 'feature.shareFranz.shareText.twitter', |
43 | defaultMessage: '!!! I\'ve added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger', | 43 | defaultMessage: '!!! I\'ve added {count} services to Ferdi! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @FranzMessenger', |
44 | }, | 44 | }, |
45 | }); | 45 | }); |
46 | 46 | ||
diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js index 34475f674..9add0f65e 100644 --- a/src/features/shareFranz/index.js +++ b/src/features/shareFranz/index.js | |||
@@ -1,8 +1,6 @@ | |||
1 | import { reaction } from 'mobx'; | 1 | import { reaction } from 'mobx'; |
2 | import ms from 'ms'; | 2 | import ms from 'ms'; |
3 | import { state as ModalState } from './store'; | 3 | import { state as ModalState } from './store'; |
4 | import { state as delayAppState } from '../delayApp'; | ||
5 | import { planSelectionStore } from '../planSelection'; | ||
6 | 4 | ||
7 | export { default as Component } from './Component'; | 5 | export { default as Component } from './Component'; |
8 | 6 | ||
@@ -19,21 +17,14 @@ export default function initialize(stores) { | |||
19 | 17 | ||
20 | function showModal() { | 18 | function showModal() { |
21 | debug('Would have showed share window'); | 19 | debug('Would have showed share window'); |
22 | |||
23 | // state.isModalVisible = true; | ||
24 | } | 20 | } |
25 | 21 | ||
26 | reaction( | 22 | reaction( |
27 | () => stores.user.isLoggedIn, | 23 | () => stores.user.isLoggedIn, |
28 | () => { | 24 | () => { |
29 | setTimeout(() => { | 25 | setTimeout(() => { |
30 | if (stores.settings.stats.appStarts % 50 === 0 && !planSelectionStore.showPlanSelectionOverlay) { | 26 | if (stores.settings.stats.appStarts % 50 === 0) { |
31 | if (delayAppState.isDelayAppScreenVisible) { | 27 | showModal(); |
32 | debug('Delaying share modal by 5 minutes'); | ||
33 | setTimeout(() => showModal(), ms('5m')); | ||
34 | } else { | ||
35 | showModal(); | ||
36 | } | ||
37 | } | 28 | } |
38 | }, ms('2s')); | 29 | }, ms('2s')); |
39 | }, | 30 | }, |
diff --git a/src/features/spellchecker/index.js b/src/features/spellchecker/index.js deleted file mode 100644 index 6a393e250..000000000 --- a/src/features/spellchecker/index.js +++ /dev/null | |||
@@ -1,27 +0,0 @@ | |||
1 | import { autorun, observable } from 'mobx'; | ||
2 | |||
3 | import { DEFAULT_FEATURES_CONFIG } from '../../config'; | ||
4 | |||
5 | const debug = require('debug')('Ferdi:feature:spellchecker'); | ||
6 | |||
7 | export const config = observable({ | ||
8 | isIncludedInCurrentPlan: DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan, | ||
9 | }); | ||
10 | |||
11 | export default function init() { | ||
12 | debug('Initializing `spellchecker` feature'); | ||
13 | |||
14 | autorun(() => { | ||
15 | // const { isSpellcheckerIncludedInCurrentPlan } = stores.features.features; | ||
16 | |||
17 | // config.isIncludedInCurrentPlan = isSpellcheckerIncludedInCurrentPlan !== undefined ? isSpellcheckerIncludedInCurrentPlan : DEFAULT_FEATURES_CONFIG.isSpellcheckerIncludedInCurrentPlan; | ||
18 | |||
19 | // if (!stores.user.data.isPremium && config.isIncludedInCurrentPlan && stores.settings.app.enableSpellchecking) { | ||
20 | // debug('Override settings.spellcheckerEnabled flag to false'); | ||
21 | |||
22 | // Object.assign(stores.settings.app, { | ||
23 | // enableSpellchecking: false, | ||
24 | // }); | ||
25 | // } | ||
26 | }); | ||
27 | } | ||
diff --git a/src/features/todos/components/TodosWebview.js b/src/features/todos/components/TodosWebview.js index ca8460f94..2dc30cdf2 100644 --- a/src/features/todos/components/TodosWebview.js +++ b/src/features/todos/components/TodosWebview.js | |||
@@ -35,26 +35,6 @@ const styles = (theme) => ({ | |||
35 | zIndex: 400, | 35 | zIndex: 400, |
36 | background: theme.todos.dragIndicator.background, | 36 | background: theme.todos.dragIndicator.background, |
37 | }, | 37 | }, |
38 | premiumContainer: { | ||
39 | display: 'flex', | ||
40 | flexDirection: 'column', | ||
41 | justifyContent: 'center', | ||
42 | alignItems: 'center', | ||
43 | width: '80%', | ||
44 | maxWidth: 300, | ||
45 | margin: [0, 'auto'], | ||
46 | textAlign: 'center', | ||
47 | }, | ||
48 | premiumIcon: { | ||
49 | marginBottom: 40, | ||
50 | background: theme.styleTypes.primary.accent, | ||
51 | fill: theme.styleTypes.primary.contrast, | ||
52 | padding: 10, | ||
53 | borderRadius: 10, | ||
54 | }, | ||
55 | premiumCTA: { | ||
56 | marginTop: 40, | ||
57 | }, | ||
58 | isTodosServiceActive: { | 38 | isTodosServiceActive: { |
59 | width: 'calc(100% - 368px)', | 39 | width: 'calc(100% - 368px)', |
60 | position: 'absolute', | 40 | position: 'absolute', |
diff --git a/src/features/todos/store.js b/src/features/todos/store.js index 429507927..abaec677f 100644 --- a/src/features/todos/store.js +++ b/src/features/todos/store.js | |||
@@ -20,7 +20,6 @@ import { FeatureStore } from '../utils/FeatureStore'; | |||
20 | import { createReactions } from '../../stores/lib/Reaction'; | 20 | import { createReactions } from '../../stores/lib/Reaction'; |
21 | import { createActionBindings } from '../utils/ActionBinding'; | 21 | import { createActionBindings } from '../utils/ActionBinding'; |
22 | import { IPC, TODOS_ROUTES } from './constants'; | 22 | import { IPC, TODOS_ROUTES } from './constants'; |
23 | import { state as delayAppState } from '../delayApp'; | ||
24 | import UserAgent from '../../models/UserAgent'; | 23 | import UserAgent from '../../models/UserAgent'; |
25 | 24 | ||
26 | const debug = require('debug')('Ferdi:feature:todos:store'); | 25 | const debug = require('debug')('Ferdi:feature:todos:store'); |
@@ -46,7 +45,7 @@ export default class TodoStore extends FeatureStore { | |||
46 | 45 | ||
47 | @computed get isTodosPanelForceHidden() { | 46 | @computed get isTodosPanelForceHidden() { |
48 | const { isAnnouncementShown } = this.stores.announcements; | 47 | const { isAnnouncementShown } = this.stores.announcements; |
49 | return delayAppState.isDelayAppScreenVisible || !this.isFeatureEnabledByUser || isAnnouncementShown; | 48 | return !this.isFeatureEnabledByUser || isAnnouncementShown; |
50 | } | 49 | } |
51 | 50 | ||
52 | @computed get isTodosPanelVisible() { | 51 | @computed get isTodosPanelVisible() { |
diff --git a/src/features/trialStatusBar/actions.js b/src/features/trialStatusBar/actions.js deleted file mode 100644 index 38df76458..000000000 --- a/src/features/trialStatusBar/actions.js +++ /dev/null | |||
@@ -1,13 +0,0 @@ | |||
1 | import PropTypes from 'prop-types'; | ||
2 | import { createActionsFromDefinitions } from '../../actions/lib/actions'; | ||
3 | |||
4 | export const trialStatusBarActions = createActionsFromDefinitions({ | ||
5 | upgradeAccount: { | ||
6 | planId: PropTypes.string.isRequired, | ||
7 | onCloseWindow: PropTypes.func.isRequired, | ||
8 | }, | ||
9 | downgradeAccount: {}, | ||
10 | hideOverlay: {}, | ||
11 | }, PropTypes.checkPropTypes); | ||
12 | |||
13 | export default trialStatusBarActions; | ||
diff --git a/src/features/trialStatusBar/components/ProgressBar.js b/src/features/trialStatusBar/components/ProgressBar.js deleted file mode 100644 index 41b74d396..000000000 --- a/src/features/trialStatusBar/components/ProgressBar.js +++ /dev/null | |||
@@ -1,45 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | |||
6 | const styles = theme => ({ | ||
7 | root: { | ||
8 | background: theme.trialStatusBar.progressBar.background, | ||
9 | width: '25%', | ||
10 | maxWidth: 200, | ||
11 | height: 8, | ||
12 | display: 'flex', | ||
13 | alignItems: 'center', | ||
14 | borderRadius: theme.borderRadius, | ||
15 | overflow: 'hidden', | ||
16 | }, | ||
17 | progress: { | ||
18 | background: theme.trialStatusBar.progressBar.progressIndicator, | ||
19 | width: ({ percent }) => `${percent}%`, | ||
20 | height: '100%', | ||
21 | }, | ||
22 | }); | ||
23 | |||
24 | @injectSheet(styles) @observer | ||
25 | class ProgressBar extends Component { | ||
26 | static propTypes = { | ||
27 | classes: PropTypes.object.isRequired, | ||
28 | }; | ||
29 | |||
30 | render() { | ||
31 | const { | ||
32 | classes, | ||
33 | } = this.props; | ||
34 | |||
35 | return ( | ||
36 | <div | ||
37 | className={classes.root} | ||
38 | > | ||
39 | <div className={classes.progress} /> | ||
40 | </div> | ||
41 | ); | ||
42 | } | ||
43 | } | ||
44 | |||
45 | export default ProgressBar; | ||
diff --git a/src/features/trialStatusBar/components/TrialStatusBar.js b/src/features/trialStatusBar/components/TrialStatusBar.js deleted file mode 100644 index b8fe4acc9..000000000 --- a/src/features/trialStatusBar/components/TrialStatusBar.js +++ /dev/null | |||
@@ -1,135 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import { Icon } from '@meetfranz/ui'; | ||
7 | import { mdiArrowRight, mdiWindowClose } from '@mdi/js'; | ||
8 | import classnames from 'classnames'; | ||
9 | |||
10 | import ProgressBar from './ProgressBar'; | ||
11 | |||
12 | const messages = defineMessages({ | ||
13 | restTime: { | ||
14 | id: 'feature.trialStatusBar.restTime', | ||
15 | defaultMessage: '!!!Your Free Franz {plan} Trial ends in {time}.', | ||
16 | }, | ||
17 | expired: { | ||
18 | id: 'feature.trialStatusBar.expired', | ||
19 | defaultMessage: '!!!Your free Franz {plan} Trial has expired, please upgrade your account.', | ||
20 | }, | ||
21 | cta: { | ||
22 | id: 'feature.trialStatusBar.cta', | ||
23 | defaultMessage: '!!!Upgrade now', | ||
24 | }, | ||
25 | }); | ||
26 | |||
27 | const styles = theme => ({ | ||
28 | root: { | ||
29 | background: theme.trialStatusBar.bar.background, | ||
30 | width: '100%', | ||
31 | height: 25, | ||
32 | order: 10, | ||
33 | display: 'flex', | ||
34 | alignItems: 'center', | ||
35 | fontSize: 12, | ||
36 | padding: [0, 10], | ||
37 | justifyContent: 'flex-end', | ||
38 | }, | ||
39 | ended: { | ||
40 | background: theme.styleTypes.warning.accent, | ||
41 | color: theme.styleTypes.warning.contrast, | ||
42 | }, | ||
43 | message: { | ||
44 | marginLeft: 20, | ||
45 | }, | ||
46 | action: { | ||
47 | marginLeft: 20, | ||
48 | fontSize: 12, | ||
49 | color: theme.colorText, | ||
50 | textDecoration: 'underline', | ||
51 | display: 'flex', | ||
52 | |||
53 | '& svg': { | ||
54 | margin: [1, 2, 0, 0], | ||
55 | }, | ||
56 | }, | ||
57 | }); | ||
58 | |||
59 | @injectSheet(styles) @observer | ||
60 | class TrialStatusBar extends Component { | ||
61 | static propTypes = { | ||
62 | planName: PropTypes.string.isRequired, | ||
63 | percent: PropTypes.number.isRequired, | ||
64 | upgradeAccount: PropTypes.func.isRequired, | ||
65 | hideOverlay: PropTypes.func.isRequired, | ||
66 | trialEnd: PropTypes.string.isRequired, | ||
67 | hasEnded: PropTypes.bool.isRequired, | ||
68 | classes: PropTypes.object.isRequired, | ||
69 | }; | ||
70 | |||
71 | static contextTypes = { | ||
72 | intl: intlShape, | ||
73 | }; | ||
74 | |||
75 | render() { | ||
76 | const { | ||
77 | planName, | ||
78 | percent, | ||
79 | upgradeAccount, | ||
80 | hideOverlay, | ||
81 | trialEnd, | ||
82 | hasEnded, | ||
83 | classes, | ||
84 | } = this.props; | ||
85 | |||
86 | const { intl } = this.context; | ||
87 | |||
88 | return ( | ||
89 | <div | ||
90 | className={classnames({ | ||
91 | [classes.root]: true, | ||
92 | [classes.ended]: hasEnded, | ||
93 | })} | ||
94 | > | ||
95 | <ProgressBar | ||
96 | percent={percent} | ||
97 | /> | ||
98 | {' '} | ||
99 | <span className={classes.message}> | ||
100 | {!hasEnded ? ( | ||
101 | intl.formatMessage(messages.restTime, { | ||
102 | plan: planName, | ||
103 | time: trialEnd, | ||
104 | }) | ||
105 | ) : ( | ||
106 | intl.formatMessage(messages.expired, { | ||
107 | plan: planName, | ||
108 | }) | ||
109 | )} | ||
110 | </span> | ||
111 | <button | ||
112 | className={classes.action} | ||
113 | type="button" | ||
114 | onClick={() => { | ||
115 | upgradeAccount(); | ||
116 | }} | ||
117 | > | ||
118 | <Icon icon={mdiArrowRight} /> | ||
119 | {intl.formatMessage(messages.cta)} | ||
120 | </button> | ||
121 | <button | ||
122 | className={classes.action} | ||
123 | type="button" | ||
124 | onClick={() => { | ||
125 | hideOverlay(); | ||
126 | }} | ||
127 | > | ||
128 | <Icon icon={mdiWindowClose} /> | ||
129 | </button> | ||
130 | </div> | ||
131 | ); | ||
132 | } | ||
133 | } | ||
134 | |||
135 | export default TrialStatusBar; | ||
diff --git a/src/features/trialStatusBar/containers/TrialStatusBarScreen.js b/src/features/trialStatusBar/containers/TrialStatusBarScreen.js deleted file mode 100644 index e0f5ab5f2..000000000 --- a/src/features/trialStatusBar/containers/TrialStatusBarScreen.js +++ /dev/null | |||
@@ -1,112 +0,0 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import { observer, inject } from 'mobx-react'; | ||
3 | import PropTypes from 'prop-types'; | ||
4 | import ms from 'ms'; | ||
5 | import { intlShape } from 'react-intl'; | ||
6 | |||
7 | import FeaturesStore from '../../../stores/FeaturesStore'; | ||
8 | import UserStore from '../../../stores/UserStore'; | ||
9 | import TrialStatusBar from '../components/TrialStatusBar'; | ||
10 | import ErrorBoundary from '../../../components/util/ErrorBoundary'; | ||
11 | import { trialStatusBarStore } from '..'; | ||
12 | import { i18nPlanName } from '../../../helpers/plan-helpers'; | ||
13 | import PaymentStore from '../../../stores/PaymentStore'; | ||
14 | |||
15 | @inject('stores', 'actions') | ||
16 | @observer | ||
17 | class TrialStatusBarScreen extends Component { | ||
18 | static contextTypes = { | ||
19 | intl: intlShape, | ||
20 | }; | ||
21 | |||
22 | state = { | ||
23 | showOverlay: true, | ||
24 | percent: 0, | ||
25 | restTime: '', | ||
26 | hasEnded: false, | ||
27 | }; | ||
28 | |||
29 | percentInterval = null; | ||
30 | |||
31 | componentDidMount() { | ||
32 | this.percentInterval = setInterval(() => { | ||
33 | this.calculateRestTime(); | ||
34 | }, ms('1m')); | ||
35 | |||
36 | this.calculateRestTime(); | ||
37 | } | ||
38 | |||
39 | componentWillUnmount() { | ||
40 | clearInterval(this.percentInterval); | ||
41 | } | ||
42 | |||
43 | calculateRestTime() { | ||
44 | const { trialEndTime } = trialStatusBarStore; | ||
45 | const percent = ( | ||
46 | Math.abs(100 - Math.abs(trialEndTime.asMilliseconds()) * 100) / ms('14d') | ||
47 | ).toFixed(2); | ||
48 | const restTime = trialEndTime.humanize(); | ||
49 | const hasEnded = trialEndTime.asMilliseconds() > 0; | ||
50 | |||
51 | this.setState({ | ||
52 | percent, | ||
53 | restTime, | ||
54 | hasEnded, | ||
55 | }); | ||
56 | } | ||
57 | |||
58 | hideOverlay() { | ||
59 | this.setState({ | ||
60 | showOverlay: false, | ||
61 | }); | ||
62 | } | ||
63 | |||
64 | render() { | ||
65 | const { intl } = this.context; | ||
66 | |||
67 | const { | ||
68 | showOverlay, percent, restTime, hasEnded, | ||
69 | } = this.state; | ||
70 | |||
71 | if ( | ||
72 | !trialStatusBarStore | ||
73 | || !trialStatusBarStore.isFeatureActive | ||
74 | || !showOverlay | ||
75 | || !trialStatusBarStore.showTrialStatusBarOverlay | ||
76 | ) { | ||
77 | return null; | ||
78 | } | ||
79 | |||
80 | const { user } = this.props.stores; | ||
81 | const { upgradeAccount } = this.props.actions.payment; | ||
82 | |||
83 | const planName = i18nPlanName(user.team.plan, intl); | ||
84 | |||
85 | return ( | ||
86 | <ErrorBoundary> | ||
87 | <TrialStatusBar | ||
88 | planName={planName} | ||
89 | percent={parseFloat(percent < 5 ? 5 : percent)} | ||
90 | trialEnd={restTime} | ||
91 | upgradeAccount={() => upgradeAccount({ | ||
92 | planId: user.team.plan, | ||
93 | })} | ||
94 | hideOverlay={() => this.hideOverlay()} | ||
95 | hasEnded={hasEnded} | ||
96 | /> | ||
97 | </ErrorBoundary> | ||
98 | ); | ||
99 | } | ||
100 | } | ||
101 | |||
102 | export default TrialStatusBarScreen; | ||
103 | |||
104 | TrialStatusBarScreen.wrappedComponent.propTypes = { | ||
105 | stores: PropTypes.shape({ | ||
106 | features: PropTypes.instanceOf(FeaturesStore).isRequired, | ||
107 | user: PropTypes.instanceOf(UserStore).isRequired, | ||
108 | }).isRequired, | ||
109 | actions: PropTypes.shape({ | ||
110 | payment: PropTypes.instanceOf(PaymentStore), | ||
111 | }).isRequired, | ||
112 | }; | ||
diff --git a/src/features/trialStatusBar/index.js b/src/features/trialStatusBar/index.js deleted file mode 100644 index 987b5c04e..000000000 --- a/src/features/trialStatusBar/index.js +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | import { reaction } from 'mobx'; | ||
2 | import TrialStatusBarStore from './store'; | ||
3 | |||
4 | const debug = require('debug')('Ferdi:feature:trialStatusBar'); | ||
5 | |||
6 | export const GA_CATEGORY_TRIAL_STATUS_BAR = 'trialStatusBar'; | ||
7 | |||
8 | export const trialStatusBarStore = new TrialStatusBarStore(); | ||
9 | |||
10 | export default function initTrialStatusBar(stores, actions) { | ||
11 | stores.trialStatusBar = trialStatusBarStore; | ||
12 | const { features } = stores; | ||
13 | |||
14 | // Toggle trialStatusBar feature | ||
15 | reaction( | ||
16 | () => features.features.isTrialStatusBarEnabled, | ||
17 | (isEnabled) => { | ||
18 | if (isEnabled) { | ||
19 | debug('Initializing `trialStatusBar` feature'); | ||
20 | trialStatusBarStore.start(stores, actions); | ||
21 | } else if (trialStatusBarStore.isFeatureActive) { | ||
22 | debug('Disabling `trialStatusBar` feature'); | ||
23 | trialStatusBarStore.stop(); | ||
24 | } | ||
25 | }, | ||
26 | { | ||
27 | fireImmediately: true, | ||
28 | }, | ||
29 | ); | ||
30 | } | ||
diff --git a/src/features/trialStatusBar/store.js b/src/features/trialStatusBar/store.js deleted file mode 100644 index 858a08238..000000000 --- a/src/features/trialStatusBar/store.js +++ /dev/null | |||
@@ -1,72 +0,0 @@ | |||
1 | import { | ||
2 | action, | ||
3 | observable, | ||
4 | computed, | ||
5 | } from 'mobx'; | ||
6 | import moment from 'moment'; | ||
7 | |||
8 | import { trialStatusBarActions } from './actions'; | ||
9 | import { FeatureStore } from '../utils/FeatureStore'; | ||
10 | import { createActionBindings } from '../utils/ActionBinding'; | ||
11 | |||
12 | const debug = require('debug')('Ferdi:feature:trialStatusBar:store'); | ||
13 | |||
14 | export default class TrialStatusBarStore extends FeatureStore { | ||
15 | @observable isFeatureActive = false; | ||
16 | |||
17 | @observable isFeatureEnabled = false; | ||
18 | |||
19 | @computed get showTrialStatusBarOverlay() { | ||
20 | if (this.isFeatureActive) { | ||
21 | const { team } = this.stores.user; | ||
22 | if (team && !this.hideOverlay) { | ||
23 | return team.state !== 'expired' && team.isTrial; | ||
24 | } | ||
25 | } | ||
26 | |||
27 | return false; | ||
28 | } | ||
29 | |||
30 | @computed get trialEndTime() { | ||
31 | if (this.isFeatureActive) { | ||
32 | const { team } = this.stores.user; | ||
33 | |||
34 | if (team && !this.hideOverlay) { | ||
35 | return moment.duration(moment().diff(team.trialEnd)); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | return moment.duration(); | ||
40 | } | ||
41 | |||
42 | // ========== PUBLIC API ========= // | ||
43 | |||
44 | @action start(stores, actions, api) { | ||
45 | debug('TrialStatusBarStore::start'); | ||
46 | this.stores = stores; | ||
47 | this.actions = actions; | ||
48 | this.api = api; | ||
49 | |||
50 | // ACTIONS | ||
51 | |||
52 | this._registerActions(createActionBindings([ | ||
53 | [trialStatusBarActions.hideOverlay, this._hideOverlay], | ||
54 | ])); | ||
55 | |||
56 | this.isFeatureActive = true; | ||
57 | } | ||
58 | |||
59 | @action stop() { | ||
60 | super.stop(); | ||
61 | debug('TrialStatusBarStore::stop'); | ||
62 | this.isFeatureActive = false; | ||
63 | } | ||
64 | |||
65 | // ========== PRIVATE METHODS ========= // | ||
66 | |||
67 | // Actions | ||
68 | |||
69 | @action _hideOverlay = () => { | ||
70 | this.hideOverlay = true; | ||
71 | } | ||
72 | } | ||
diff --git a/src/features/workspaces/components/WorkspaceDrawer.js b/src/features/workspaces/components/WorkspaceDrawer.js index bf7016e2f..3ddc9cf16 100644 --- a/src/features/workspaces/components/WorkspaceDrawer.js +++ b/src/features/workspaces/components/WorkspaceDrawer.js | |||
@@ -2,12 +2,11 @@ import React, { Component } from 'react'; | |||
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer } from 'mobx-react'; | 3 | import { observer } from 'mobx-react'; |
4 | import injectSheet from 'react-jss'; | 4 | import injectSheet from 'react-jss'; |
5 | import { defineMessages, FormattedHTMLMessage, intlShape } from 'react-intl'; | 5 | import { defineMessages, intlShape } from 'react-intl'; |
6 | import { H1, Icon, ProBadge } from '@meetfranz/ui'; | 6 | import { H1, Icon } from '@meetfranz/ui'; |
7 | import { Button } from '@meetfranz/forms/lib'; | ||
8 | import ReactTooltip from 'react-tooltip'; | 7 | import ReactTooltip from 'react-tooltip'; |
9 | 8 | ||
10 | import { mdiPlusBox, mdiSettings, mdiStar } from '@mdi/js'; | 9 | import { mdiPlusBox, mdiSettings } from '@mdi/js'; |
11 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; | 10 | import WorkspaceDrawerItem from './WorkspaceDrawerItem'; |
12 | import { workspaceActions } from '../actions'; | 11 | import { workspaceActions } from '../actions'; |
13 | import { workspaceStore } from '../index'; | 12 | import { workspaceStore } from '../index'; |
@@ -29,22 +28,10 @@ const messages = defineMessages({ | |||
29 | id: 'workspaceDrawer.workspaceFeatureInfo', | 28 | id: 'workspaceDrawer.workspaceFeatureInfo', |
30 | defaultMessage: '!!!Info about workspace feature', | 29 | defaultMessage: '!!!Info about workspace feature', |
31 | }, | 30 | }, |
32 | premiumCtaButtonLabel: { | ||
33 | id: 'workspaceDrawer.premiumCtaButtonLabel', | ||
34 | defaultMessage: '!!!Create your first workspace', | ||
35 | }, | ||
36 | reactivatePremiumAccount: { | ||
37 | id: 'workspaceDrawer.reactivatePremiumAccountLabel', | ||
38 | defaultMessage: '!!!Reactivate premium account', | ||
39 | }, | ||
40 | addNewWorkspaceLabel: { | 31 | addNewWorkspaceLabel: { |
41 | id: 'workspaceDrawer.addNewWorkspaceLabel', | 32 | id: 'workspaceDrawer.addNewWorkspaceLabel', |
42 | defaultMessage: '!!!add new workspace', | 33 | defaultMessage: '!!!add new workspace', |
43 | }, | 34 | }, |
44 | premiumFeatureBadge: { | ||
45 | id: 'workspaceDrawer.proFeatureBadge', | ||
46 | defaultMessage: '!!!Premium feature', | ||
47 | }, | ||
48 | }); | 35 | }); |
49 | 36 | ||
50 | const styles = theme => ({ | 37 | const styles = theme => ({ |
@@ -60,9 +47,6 @@ const styles = theme => ({ | |||
60 | marginBottom: '25px', | 47 | marginBottom: '25px', |
61 | marginLeft: theme.workspaces.drawer.padding, | 48 | marginLeft: theme.workspaces.drawer.padding, |
62 | }, | 49 | }, |
63 | headlineProBadge: { | ||
64 | marginRight: 15, | ||
65 | }, | ||
66 | workspacesSettingsButton: { | 50 | workspacesSettingsButton: { |
67 | float: 'right', | 51 | float: 'right', |
68 | marginRight: theme.workspaces.drawer.padding, | 52 | marginRight: theme.workspaces.drawer.padding, |
@@ -78,16 +62,6 @@ const styles = theme => ({ | |||
78 | height: 'auto', | 62 | height: 'auto', |
79 | overflowY: 'auto', | 63 | overflowY: 'auto', |
80 | }, | 64 | }, |
81 | premiumAnnouncement: { | ||
82 | padding: '20px', | ||
83 | paddingTop: '0', | ||
84 | height: 'auto', | ||
85 | }, | ||
86 | premiumCtaButton: { | ||
87 | marginTop: '20px', | ||
88 | width: '100%', | ||
89 | color: 'white !important', | ||
90 | }, | ||
91 | addNewWorkspaceLabel: { | 65 | addNewWorkspaceLabel: { |
92 | height: 'auto', | 66 | height: 'auto', |
93 | color: theme.workspaces.drawer.buttons.color, | 67 | color: theme.workspaces.drawer.buttons.color, |
@@ -116,7 +90,6 @@ class WorkspaceDrawer extends Component { | |||
116 | static propTypes = { | 90 | static propTypes = { |
117 | classes: PropTypes.object.isRequired, | 91 | classes: PropTypes.object.isRequired, |
118 | getServicesForWorkspace: PropTypes.func.isRequired, | 92 | getServicesForWorkspace: PropTypes.func.isRequired, |
119 | onUpgradeAccountClick: PropTypes.func.isRequired, | ||
120 | }; | 93 | }; |
121 | 94 | ||
122 | static contextTypes = { | 95 | static contextTypes = { |
@@ -131,7 +104,6 @@ class WorkspaceDrawer extends Component { | |||
131 | const { | 104 | const { |
132 | classes, | 105 | classes, |
133 | getServicesForWorkspace, | 106 | getServicesForWorkspace, |
134 | onUpgradeAccountClick, | ||
135 | } = this.props; | 107 | } = this.props; |
136 | const { intl } = this.context; | 108 | const { intl } = this.context; |
137 | const { | 109 | const { |
@@ -144,14 +116,6 @@ class WorkspaceDrawer extends Component { | |||
144 | return ( | 116 | return ( |
145 | <div className={`${classes.drawer} workspaces-drawer`}> | 117 | <div className={`${classes.drawer} workspaces-drawer`}> |
146 | <H1 className={classes.headline}> | 118 | <H1 className={classes.headline}> |
147 | {workspaceStore.isPremiumUpgradeRequired && ( | ||
148 | <span | ||
149 | className={classes.headlineProBadge} | ||
150 | data-tip={`${intl.formatMessage(messages.premiumFeatureBadge)}`} | ||
151 | > | ||
152 | <ProBadge /> | ||
153 | </span> | ||
154 | )} | ||
155 | {intl.formatMessage(messages.headline)} | 119 | {intl.formatMessage(messages.headline)} |
156 | <span | 120 | <span |
157 | className={classes.workspacesSettingsButton} | 121 | className={classes.workspacesSettingsButton} |
@@ -167,75 +131,48 @@ class WorkspaceDrawer extends Component { | |||
167 | /> | 131 | /> |
168 | </span> | 132 | </span> |
169 | </H1> | 133 | </H1> |
170 | {workspaceStore.isPremiumUpgradeRequired ? ( | 134 | <div className={classes.workspaces}> |
171 | <div className={classes.premiumAnnouncement}> | 135 | <WorkspaceDrawerItem |
172 | <FormattedHTMLMessage {...messages.workspaceFeatureInfo} /> | 136 | name={intl.formatMessage(messages.allServices)} |
173 | {workspaceStore.userHasWorkspaces ? ( | 137 | onClick={() => { |
174 | <Button | 138 | workspaceActions.deactivate(); |
175 | className={classes.premiumCtaButton} | 139 | workspaceActions.toggleWorkspaceDrawer(); |
176 | buttonType="primary" | 140 | }} |
177 | label={intl.formatMessage(messages.reactivatePremiumAccount)} | 141 | services={getServicesForWorkspace(null)} |
178 | icon={mdiStar} | 142 | isActive={actualWorkspace == null} |
179 | onClick={() => { | 143 | shortcutIndex={0} |
180 | onUpgradeAccountClick(); | 144 | /> |
181 | }} | 145 | {workspaces.map((workspace, index) => ( |
182 | /> | ||
183 | ) : ( | ||
184 | <Button | ||
185 | className={classes.premiumCtaButton} | ||
186 | buttonType="primary" | ||
187 | label={intl.formatMessage(messages.premiumCtaButtonLabel)} | ||
188 | icon={mdiPlusBox} | ||
189 | onClick={() => { | ||
190 | workspaceActions.openWorkspaceSettings(); | ||
191 | }} | ||
192 | /> | ||
193 | )} | ||
194 | </div> | ||
195 | ) : ( | ||
196 | <div className={classes.workspaces}> | ||
197 | <WorkspaceDrawerItem | 146 | <WorkspaceDrawerItem |
198 | name={intl.formatMessage(messages.allServices)} | 147 | key={workspace.id} |
148 | name={workspace.name} | ||
149 | isActive={actualWorkspace === workspace} | ||
199 | onClick={() => { | 150 | onClick={() => { |
200 | workspaceActions.deactivate(); | 151 | if (actualWorkspace === workspace) return; |
152 | workspaceActions.activate({ workspace }); | ||
201 | workspaceActions.toggleWorkspaceDrawer(); | 153 | workspaceActions.toggleWorkspaceDrawer(); |
202 | }} | 154 | }} |
203 | services={getServicesForWorkspace(null)} | 155 | onContextMenuEditClick={() => workspaceActions.edit({ workspace })} |
204 | isActive={actualWorkspace == null} | 156 | services={getServicesForWorkspace(workspace)} |
205 | shortcutIndex={0} | 157 | shortcutIndex={index + 1} |
206 | /> | 158 | /> |
207 | {workspaces.map((workspace, index) => ( | 159 | ))} |
208 | <WorkspaceDrawerItem | 160 | <div |
209 | key={workspace.id} | 161 | className={classes.addNewWorkspaceLabel} |
210 | name={workspace.name} | 162 | onClick={() => { |
211 | isActive={actualWorkspace === workspace} | 163 | workspaceActions.openWorkspaceSettings(); |
212 | onClick={() => { | 164 | }} |
213 | if (actualWorkspace === workspace) return; | 165 | > |
214 | workspaceActions.activate({ workspace }); | 166 | <Icon |
215 | workspaceActions.toggleWorkspaceDrawer(); | 167 | icon={mdiPlusBox} |
216 | }} | 168 | size={1} |
217 | onContextMenuEditClick={() => workspaceActions.edit({ workspace })} | 169 | className={classes.workspacesSettingsButtonIcon} |
218 | services={getServicesForWorkspace(workspace)} | 170 | /> |
219 | shortcutIndex={index + 1} | 171 | <span> |
220 | /> | 172 | {intl.formatMessage(messages.addNewWorkspaceLabel)} |
221 | ))} | 173 | </span> |
222 | <div | ||
223 | className={classes.addNewWorkspaceLabel} | ||
224 | onClick={() => { | ||
225 | workspaceActions.openWorkspaceSettings(); | ||
226 | }} | ||
227 | > | ||
228 | <Icon | ||
229 | icon={mdiPlusBox} | ||
230 | size={1} | ||
231 | className={classes.workspacesSettingsButtonIcon} | ||
232 | /> | ||
233 | <span> | ||
234 | {intl.formatMessage(messages.addNewWorkspaceLabel)} | ||
235 | </span> | ||
236 | </div> | ||
237 | </div> | 174 | </div> |
238 | )} | 175 | </div> |
239 | <ReactTooltip place="right" type="dark" effect="solid" /> | 176 | <ReactTooltip place="right" type="dark" effect="solid" /> |
240 | </div> | 177 | </div> |
241 | ); | 178 | ); |
diff --git a/src/features/workspaces/components/WorkspacesDashboard.js b/src/features/workspaces/components/WorkspacesDashboard.js index cfaacd56e..0c2588c42 100644 --- a/src/features/workspaces/components/WorkspacesDashboard.js +++ b/src/features/workspaces/components/WorkspacesDashboard.js | |||
@@ -1,9 +1,9 @@ | |||
1 | import React, { Component, Fragment } from 'react'; | 1 | import React, { Component } from 'react'; |
2 | import PropTypes from 'prop-types'; | 2 | import PropTypes from 'prop-types'; |
3 | import { observer, PropTypes as MobxPropTypes, inject } 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, Badge } from '@meetfranz/ui'; | 6 | import { Infobox } from '@meetfranz/ui'; |
7 | 7 | ||
8 | import { mdiCheckboxMarkedCircleOutline } from '@mdi/js'; | 8 | import { mdiCheckboxMarkedCircleOutline } from '@mdi/js'; |
9 | import Loader from '../../../components/ui/Loader'; | 9 | import Loader from '../../../components/ui/Loader'; |
@@ -11,10 +11,7 @@ import WorkspaceItem from './WorkspaceItem'; | |||
11 | import CreateWorkspaceForm from './CreateWorkspaceForm'; | 11 | import CreateWorkspaceForm from './CreateWorkspaceForm'; |
12 | import Request from '../../../stores/lib/Request'; | 12 | import Request from '../../../stores/lib/Request'; |
13 | import Appear from '../../../components/ui/effects/Appear'; | 13 | import Appear from '../../../components/ui/effects/Appear'; |
14 | import { workspaceStore } from '../index'; | ||
15 | import UIStore from '../../../stores/UIStore'; | 14 | import UIStore from '../../../stores/UIStore'; |
16 | import globalMessages from '../../../i18n/globalMessages'; | ||
17 | import UpgradeButton from '../../../components/ui/UpgradeButton'; | ||
18 | 15 | ||
19 | const messages = defineMessages({ | 16 | const messages = defineMessages({ |
20 | headline: { | 17 | headline: { |
@@ -64,12 +61,6 @@ const styles = () => ({ | |||
64 | appear: { | 61 | appear: { |
65 | height: 'auto', | 62 | height: 'auto', |
66 | }, | 63 | }, |
67 | premiumAnnouncement: { | ||
68 | height: 'auto', | ||
69 | }, | ||
70 | premiumAnnouncementContainer: { | ||
71 | display: 'flex', | ||
72 | }, | ||
73 | announcementHeadline: { | 64 | announcementHeadline: { |
74 | marginBottom: 0, | 65 | marginBottom: 0, |
75 | }, | 66 | }, |
@@ -78,12 +69,6 @@ const styles = () => ({ | |||
78 | margin: [-8, 0, 0, 20], | 69 | margin: [-8, 0, 0, 20], |
79 | alignSelf: 'center', | 70 | alignSelf: 'center', |
80 | }, | 71 | }, |
81 | upgradeCTA: { | ||
82 | margin: [40, 'auto'], | ||
83 | }, | ||
84 | proRequired: { | ||
85 | margin: [10, 0, 40], | ||
86 | }, | ||
87 | }); | 72 | }); |
88 | 73 | ||
89 | @inject('stores') @injectSheet(styles) @observer | 74 | @inject('stores') @injectSheet(styles) @observer |
@@ -152,77 +137,53 @@ class WorkspacesDashboard extends Component { | |||
152 | </Appear> | 137 | </Appear> |
153 | )} | 138 | )} |
154 | 139 | ||
155 | {workspaceStore.isPremiumUpgradeRequired && ( | 140 | {/* ===== Create workspace form ===== */} |
156 | <div className={classes.premiumAnnouncement}> | 141 | <div className={classes.createForm}> |
157 | 142 | <CreateWorkspaceForm | |
158 | <h1 className={classes.announcementHeadline}>{intl.formatMessage(messages.workspaceFeatureHeadline)}</h1> | 143 | isSubmitting={createWorkspaceRequest.isExecuting} |
159 | <Badge className={classes.proRequired}>{intl.formatMessage(globalMessages.proRequired)}</Badge> | 144 | onSubmit={onCreateWorkspaceSubmit} |
160 | <div className={classes.premiumAnnouncementContainer}> | 145 | /> |
161 | <div className={classes.premiumAnnouncementContent}> | 146 | </div> |
162 | <p>{intl.formatMessage(messages.workspaceFeatureInfo)}</p> | 147 | {getUserWorkspacesRequest.isExecuting ? ( |
163 | <UpgradeButton | 148 | <Loader /> |
164 | className={classes.upgradeCTA} | 149 | ) : ( |
165 | gaEventInfo={{ category: 'Workspaces', event: 'upgrade' }} | ||
166 | short | ||
167 | requiresPro | ||
168 | /> | ||
169 | </div> | ||
170 | <img src={`https://cdn.franzinfra.com/announcements/assets/workspaces_${this.props.stores.ui.isDarkThemeActive ? 'dark' : 'light'}.png`} className={classes.teaserImage} alt="" /> | ||
171 | </div> | ||
172 | </div> | ||
173 | )} | ||
174 | |||
175 | {!workspaceStore.isPremiumUpgradeRequired && ( | ||
176 | <> | 150 | <> |
177 | {/* ===== Create workspace form ===== */} | 151 | {/* ===== Workspace could not be loaded error ===== */} |
178 | <div className={classes.createForm}> | 152 | {getUserWorkspacesRequest.error ? ( |
179 | <CreateWorkspaceForm | 153 | <Infobox |
180 | isSubmitting={createWorkspaceRequest.isExecuting} | 154 | icon="alert" |
181 | onSubmit={onCreateWorkspaceSubmit} | 155 | type="danger" |
182 | /> | 156 | ctaLabel={intl.formatMessage(messages.tryReloadWorkspaces)} |
183 | </div> | 157 | ctaLoading={getUserWorkspacesRequest.isExecuting} |
184 | {getUserWorkspacesRequest.isExecuting ? ( | 158 | ctaOnClick={getUserWorkspacesRequest.retry} |
185 | <Loader /> | 159 | > |
160 | {intl.formatMessage(messages.workspacesRequestFailed)} | ||
161 | </Infobox> | ||
186 | ) : ( | 162 | ) : ( |
187 | <> | 163 | <> |
188 | {/* ===== Workspace could not be loaded error ===== */} | 164 | {workspaces.length === 0 ? ( |
189 | {getUserWorkspacesRequest.error ? ( | 165 | <div className="align-middle settings__empty-state"> |
190 | <Infobox | 166 | {/* ===== Workspaces empty state ===== */} |
191 | icon="alert" | 167 | <p className="settings__empty-text"> |
192 | type="danger" | 168 | <span className="emoji"> |
193 | ctaLabel={intl.formatMessage(messages.tryReloadWorkspaces)} | 169 | <img src="./assets/images/emoji/sad.png" alt="" /> |
194 | ctaLoading={getUserWorkspacesRequest.isExecuting} | 170 | </span> |
195 | ctaOnClick={getUserWorkspacesRequest.retry} | 171 | {intl.formatMessage(messages.noServicesAdded)} |
196 | > | 172 | </p> |
197 | {intl.formatMessage(messages.workspacesRequestFailed)} | 173 | </div> |
198 | </Infobox> | ||
199 | ) : ( | 174 | ) : ( |
200 | <> | 175 | <table className={classes.table}> |
201 | {workspaces.length === 0 ? ( | 176 | {/* ===== Workspaces list ===== */} |
202 | <div className="align-middle settings__empty-state"> | 177 | <tbody> |
203 | {/* ===== Workspaces empty state ===== */} | 178 | {workspaces.map(workspace => ( |
204 | <p className="settings__empty-text"> | 179 | <WorkspaceItem |
205 | <span className="emoji"> | 180 | key={workspace.id} |
206 | <img src="./assets/images/emoji/sad.png" alt="" /> | 181 | workspace={workspace} |
207 | </span> | 182 | onItemClick={w => onWorkspaceClick(w)} |
208 | {intl.formatMessage(messages.noServicesAdded)} | 183 | /> |
209 | </p> | 184 | ))} |
210 | </div> | 185 | </tbody> |
211 | ) : ( | 186 | </table> |
212 | <table className={classes.table}> | ||
213 | {/* ===== Workspaces list ===== */} | ||
214 | <tbody> | ||
215 | {workspaces.map(workspace => ( | ||
216 | <WorkspaceItem | ||
217 | key={workspace.id} | ||
218 | workspace={workspace} | ||
219 | onItemClick={w => onWorkspaceClick(w)} | ||
220 | /> | ||
221 | ))} | ||
222 | </tbody> | ||
223 | </table> | ||
224 | )} | ||
225 | </> | ||
226 | )} | 187 | )} |
227 | </> | 188 | </> |
228 | )} | 189 | )} |
diff --git a/src/features/workspaces/store.js b/src/features/workspaces/store.js index 632f3c299..8c73516bc 100644 --- a/src/features/workspaces/store.js +++ b/src/features/workspaces/store.js | |||
@@ -22,10 +22,6 @@ export default class WorkspacesStore extends FeatureStore { | |||
22 | 22 | ||
23 | @observable isFeatureActive = false; | 23 | @observable isFeatureActive = false; |
24 | 24 | ||
25 | @observable isPremiumFeature = false; | ||
26 | |||
27 | @observable isPremiumUpgradeRequired = false; | ||
28 | |||
29 | @observable activeWorkspace = null; | 25 | @observable activeWorkspace = null; |
30 | 26 | ||
31 | @observable nextWorkspace = null; | 27 | @observable nextWorkspace = null; |
@@ -58,7 +54,6 @@ export default class WorkspacesStore extends FeatureStore { | |||
58 | 54 | ||
59 | @computed get isUserAllowedToUseFeature() { | 55 | @computed get isUserAllowedToUseFeature() { |
60 | return true; | 56 | return true; |
61 | // return !this.isPremiumUpgradeRequired; | ||
62 | } | 57 | } |
63 | 58 | ||
64 | @computed get isAnyWorkspaceActive() { | 59 | @computed get isAnyWorkspaceActive() { |
@@ -69,16 +64,8 @@ export default class WorkspacesStore extends FeatureStore { | |||
69 | 64 | ||
70 | _wasDrawerOpenBeforeSettingsRoute = null; | 65 | _wasDrawerOpenBeforeSettingsRoute = null; |
71 | 66 | ||
72 | _freeUserActions = []; | ||
73 | |||
74 | _premiumUserActions = []; | ||
75 | |||
76 | _allActions = []; | 67 | _allActions = []; |
77 | 68 | ||
78 | _freeUserReactions = []; | ||
79 | |||
80 | _premiumUserReactions = []; | ||
81 | |||
82 | _allReactions = []; | 69 | _allReactions = []; |
83 | 70 | ||
84 | // ========== PUBLIC API ========= // | 71 | // ========== PUBLIC API ========= // |
@@ -90,11 +77,9 @@ export default class WorkspacesStore extends FeatureStore { | |||
90 | 77 | ||
91 | // ACTIONS | 78 | // ACTIONS |
92 | 79 | ||
93 | this._freeUserActions = createActionBindings([ | 80 | this._allActions = createActionBindings([ |
94 | [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer], | 81 | [workspaceActions.toggleWorkspaceDrawer, this._toggleWorkspaceDrawer], |
95 | [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings], | 82 | [workspaceActions.openWorkspaceSettings, this._openWorkspaceSettings], |
96 | ]); | ||
97 | this._premiumUserActions = createActionBindings([ | ||
98 | [workspaceActions.edit, this._edit], | 83 | [workspaceActions.edit, this._edit], |
99 | [workspaceActions.create, this._create], | 84 | [workspaceActions.create, this._create], |
100 | [workspaceActions.delete, this._delete], | 85 | [workspaceActions.delete, this._delete], |
@@ -106,27 +91,18 @@ export default class WorkspacesStore extends FeatureStore { | |||
106 | this._toggleKeepAllWorkspacesLoadedSetting, | 91 | this._toggleKeepAllWorkspacesLoadedSetting, |
107 | ], | 92 | ], |
108 | ]); | 93 | ]); |
109 | this._allActions = this._freeUserActions.concat(this._premiumUserActions); | ||
110 | this._registerActions(this._allActions); | 94 | this._registerActions(this._allActions); |
111 | 95 | ||
112 | // REACTIONS | 96 | // REACTIONS |
113 | 97 | ||
114 | this._freeUserReactions = createReactions([ | 98 | this._allReactions = createReactions([ |
115 | this._disablePremiumFeatures, | ||
116 | this._openDrawerWithSettingsReaction, | 99 | this._openDrawerWithSettingsReaction, |
117 | this._setFeatureEnabledReaction, | 100 | this._setFeatureEnabledReaction, |
118 | this._setIsPremiumFeatureReaction, | ||
119 | this._cleanupInvalidServiceReferences, | 101 | this._cleanupInvalidServiceReferences, |
120 | ]); | ||
121 | this._premiumUserReactions = createReactions([ | ||
122 | this._setActiveServiceOnWorkspaceSwitchReaction, | 102 | this._setActiveServiceOnWorkspaceSwitchReaction, |
123 | this._activateLastUsedWorkspaceReaction, | 103 | this._activateLastUsedWorkspaceReaction, |
124 | this._setWorkspaceBeingEditedReaction, | 104 | this._setWorkspaceBeingEditedReaction, |
125 | ]); | 105 | ]); |
126 | this._allReactions = this._freeUserReactions.concat( | ||
127 | this._premiumUserReactions, | ||
128 | ); | ||
129 | |||
130 | this._registerReactions(this._allReactions); | 106 | this._registerReactions(this._allReactions); |
131 | 107 | ||
132 | getUserWorkspacesRequest.execute(); | 108 | getUserWorkspacesRequest.execute(); |
@@ -273,13 +249,6 @@ export default class WorkspacesStore extends FeatureStore { | |||
273 | this.isFeatureEnabled = isWorkspaceEnabled; | 249 | this.isFeatureEnabled = isWorkspaceEnabled; |
274 | }; | 250 | }; |
275 | 251 | ||
276 | _setIsPremiumFeatureReaction = () => { | ||
277 | // const { features } = this.stores; | ||
278 | // const { isWorkspaceIncludedInCurrentPlan } = features.features; | ||
279 | // this.isPremiumFeature = !isWorkspaceIncludedInCurrentPlan; | ||
280 | // this.isPremiumUpgradeRequired = !isWorkspaceIncludedInCurrentPlan; | ||
281 | }; | ||
282 | |||
283 | _setWorkspaceBeingEditedReaction = () => { | 252 | _setWorkspaceBeingEditedReaction = () => { |
284 | const { pathname } = this.stores.router.location; | 253 | const { pathname } = this.stores.router.location; |
285 | const match = matchRoute('/settings/workspaces/edit/:id', pathname); | 254 | const match = matchRoute('/settings/workspaces/edit/:id', pathname); |
@@ -357,16 +326,4 @@ export default class WorkspacesStore extends FeatureStore { | |||
357 | }); | 326 | }); |
358 | }); | 327 | }); |
359 | }; | 328 | }; |
360 | |||
361 | _disablePremiumFeatures = () => { | ||
362 | if (!this.isUserAllowedToUseFeature) { | ||
363 | debug('_disablePremiumFeatures'); | ||
364 | this._stopActions(this._premiumUserActions); | ||
365 | this._stopReactions(this._premiumUserReactions); | ||
366 | this.reset(); | ||
367 | } else { | ||
368 | this._startActions(this._premiumUserActions); | ||
369 | this._startReactions(this._premiumUserReactions); | ||
370 | } | ||
371 | }; | ||
372 | } | 329 | } |