aboutsummaryrefslogtreecommitdiffstats
path: root/src/components
diff options
context:
space:
mode:
authorLibravatar Vijay A <avijayr@protonmail.com>2021-07-17 20:32:22 +0530
committerLibravatar Vijay Raghavan Aravamudhan <vraravam@users.noreply.github.com>2021-07-20 16:02:15 +0000
commit45373f655f68fdd0b320cde175b6108454ad4731 (patch)
treec1ccb0c73639d754b68a36a1977b74471fe4b566 /src/components
parentNew Crowdin updates (#1668) (diff)
downloadferdium-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/components')
-rw-r--r--src/components/TrialActivationInfoBar.js94
-rw-r--r--src/components/auth/Pricing.js270
-rw-r--r--src/components/layout/AppLayout.js10
-rw-r--r--src/components/services/content/ServiceRestricted.js78
-rw-r--r--src/components/services/content/Services.js5
-rw-r--r--src/components/settings/account/AccountDashboard.js147
-rw-r--r--src/components/settings/navigation/SettingsNavigation.js16
-rw-r--r--src/components/settings/recipes/RecipesDashboard.js58
-rw-r--r--src/components/settings/services/EditServiceForm.js124
-rw-r--r--src/components/settings/services/ServicesDashboard.js2
-rw-r--r--src/components/settings/settings/EditSettingsForm.js29
-rw-r--r--src/components/settings/team/TeamDashboard.js39
-rw-r--r--src/components/subscription/SubscriptionForm.js78
-rw-r--r--src/components/subscription/SubscriptionPopup.js84
-rw-r--r--src/components/subscription/TrialForm.js115
-rw-r--r--src/components/ui/ActivateTrialButton/index.js107
-rw-r--r--src/components/ui/FeatureList.js81
-rw-r--r--src/components/ui/PremiumFeatureContainer/index.js101
-rw-r--r--src/components/ui/PremiumFeatureContainer/styles.js34
-rw-r--r--src/components/ui/UpgradeButton/index.js83
20 files changed, 118 insertions, 1437 deletions
diff --git a/src/components/TrialActivationInfoBar.js b/src/components/TrialActivationInfoBar.js
deleted file mode 100644
index 77ab97565..000000000
--- a/src/components/TrialActivationInfoBar.js
+++ /dev/null
@@ -1,94 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl';
4import ms from 'ms';
5import injectSheet from 'react-jss';
6import classnames from 'classnames';
7
8import InfoBar from './ui/InfoBar';
9
10const messages = defineMessages({
11 message: {
12 id: 'infobar.trialActivated',
13 defaultMessage: '!!!Your trial was successfully activated. Happy messaging!',
14 },
15});
16
17const styles = {
18 notification: {
19 height: 'auto',
20 position: 'absolute',
21 top: -50,
22 transition: 'top 0.3s',
23 zIndex: 500,
24 width: 'calc(100% - 300px)',
25 },
26 show: {
27 top: 0,
28 },
29};
30
31@injectSheet(styles)
32class TrialActivationInfoBar extends Component {
33 static propTypes = {
34 // eslint-disable-next-line
35 classes: PropTypes.object.isRequired,
36 };
37
38 static contextTypes = {
39 intl: intlShape,
40 };
41
42 state = {
43 showing: false,
44 removed: false,
45 }
46
47 componentDidMount() {
48 setTimeout(() => {
49 this.setState({
50 showing: true,
51 });
52 }, 0);
53
54 setTimeout(() => {
55 this.setState({
56 showing: false,
57 });
58 }, ms('6s'));
59
60 setTimeout(() => {
61 this.setState({
62 removed: true,
63 });
64 }, ms('7s'));
65 }
66
67 render() {
68 const { classes } = this.props;
69 const { showing, removed } = this.state;
70 const { intl } = this.context;
71
72 if (removed) return null;
73
74 return (
75 <div
76 className={classnames({
77 [classes.notification]: true,
78 [classes.show]: showing,
79 })}
80 >
81 <InfoBar
82 type="primary"
83 position="top"
84 sticky
85 >
86 <span className="mdi mdi-information" />
87 {intl.formatMessage(messages.message)}
88 </InfoBar>
89 </div>
90 );
91 }
92}
93
94export default TrialActivationInfoBar;
diff --git a/src/components/auth/Pricing.js b/src/components/auth/Pricing.js
deleted file mode 100644
index 2fcabe54d..000000000
--- a/src/components/auth/Pricing.js
+++ /dev/null
@@ -1,270 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6import { H2, Loader } from '@meetfranz/ui';
7import classnames from 'classnames';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureItem } from '../ui/FeatureItem';
11import { FeatureList } from '../ui/FeatureList';
12
13const messages = defineMessages({
14 headline: {
15 id: 'pricing.trial.headline.pro',
16 defaultMessage: '!!!Hi {name}, welcome to Franz',
17 },
18 specialTreat: {
19 id: 'pricing.trial.intro.specialTreat',
20 defaultMessage: '!!!We have a special treat for you.',
21 },
22 tryPro: {
23 id: 'pricing.trial.intro.tryPro',
24 defaultMessage: '!!!Enjoy the full Franz Professional experience completely free for 14 days.',
25 },
26 happyMessaging: {
27 id: 'pricing.trial.intro.happyMessaging',
28 defaultMessage: '!!!Happy messaging,',
29 },
30 noStringsAttachedHeadline: {
31 id: 'pricing.trial.terms.headline',
32 defaultMessage: '!!!No strings attached',
33 },
34 noCreditCard: {
35 id: 'pricing.trial.terms.noCreditCard',
36 defaultMessage: '!!!No credit card required',
37 },
38 automaticTrialEnd: {
39 id: 'pricing.trial.terms.automaticTrialEnd',
40 defaultMessage: '!!!Your free trial ends automatically after 14 days',
41 },
42 trialWorth: {
43 id: 'pricing.trial.terms.trialWorth',
44 defaultMessage: '!!!Free trial (normally {currency}{price} per month)',
45 },
46 activationError: {
47 id: 'pricing.trial.error',
48 defaultMessage: '!!!Sorry, we could not activate your trial!',
49 },
50 ctaAccept: {
51 id: 'pricing.trial.cta.accept',
52 defaultMessage: '!!!Start my 14-day Franz Professional Trial ',
53 },
54 ctaStart: {
55 id: 'pricing.trial.cta.start',
56 defaultMessage: '!!!Start using Franz',
57 },
58 ctaSkip: {
59 id: 'pricing.trial.cta.skip',
60 defaultMessage: '!!!Continue to Ferdi',
61 },
62 featuresHeadline: {
63 id: 'pricing.trial.features.headline',
64 defaultMessage: '!!!Franz Professional includes:',
65 },
66});
67
68const styles = theme => ({
69 root: {
70 width: '500px !important',
71 textAlign: 'center',
72 padding: 20,
73 zIndex: 100,
74
75 '& h1': {
76 },
77 },
78 container: {
79 position: 'relative',
80 marginLeft: -150,
81 },
82 welcomeOffer: {
83 textAlign: 'center',
84 fontWeight: 'bold',
85 marginBottom: '6 !important',
86 },
87 keyTerms: {
88 textAlign: 'center',
89 },
90 content: {
91 position: 'relative',
92 zIndex: 20,
93 },
94 featureContainer: {
95 width: 300,
96 position: 'absolute',
97 left: 'calc(100% / 2 + 250px)',
98 marginTop: 20,
99 background: theme.signup.pricing.feature.background,
100 height: 'auto',
101 padding: 20,
102 borderTopRightRadius: theme.borderRadius,
103 borderBottomRightRadius: theme.borderRadius,
104 zIndex: 10,
105 },
106 featureItem: {
107 borderBottom: [1, 'solid', theme.signup.pricing.feature.border],
108 },
109 cta: {
110 marginTop: 40,
111 width: '100%',
112 },
113 skipLink: {
114 textAlign: 'center',
115 marginTop: 10,
116 },
117 error: {
118 margin: [20, 0, 0],
119 color: theme.styleTypes.danger.accent,
120 },
121 priceContainer: {
122 display: 'flex',
123 justifyContent: 'space-evenly',
124 margin: [10, 0, 15],
125 },
126 price: {
127 '& sup': {
128 verticalAlign: 14,
129 fontSize: 20,
130 },
131 },
132 figure: {
133 fontSize: 40,
134 },
135 regularPrice: {
136 position: 'relative',
137
138 '&:before': {
139 content: '" "',
140 position: 'absolute',
141 width: '130%',
142 height: 1,
143 top: 14,
144 left: -12,
145 borderBottom: [3, 'solid', 'red'],
146 transform: 'rotateZ(-20deg)',
147 },
148 },
149});
150
151export default @injectSheet(styles) @observer class Signup extends Component {
152 static propTypes = {
153 onSubmit: PropTypes.func.isRequired,
154 isLoadingRequiredData: PropTypes.bool.isRequired,
155 isActivatingTrial: PropTypes.bool.isRequired,
156 trialActivationError: PropTypes.bool.isRequired,
157 canSkipTrial: PropTypes.bool.isRequired,
158 classes: PropTypes.object.isRequired,
159 currency: PropTypes.string.isRequired,
160 price: PropTypes.number.isRequired,
161 name: PropTypes.string.isRequired,
162 };
163
164 static contextTypes = {
165 intl: intlShape,
166 };
167
168 render() {
169 const {
170 onSubmit,
171 isLoadingRequiredData,
172 isActivatingTrial,
173 trialActivationError,
174 canSkipTrial,
175 classes,
176 currency,
177 price,
178 name,
179 } = this.props;
180 const { intl } = this.context;
181
182 const [intPart, fractionPart] = (price).toString().split('.');
183
184 return (
185 <>
186 <div className={classnames('auth__container', classes.root, classes.container)}>
187 <form className="franz-form auth__form">
188 {isLoadingRequiredData ? <Loader /> : (
189 <img
190 src="./assets/images/sm.png"
191 className="auth__logo auth__logo--sm"
192 alt=""
193 />
194 )}
195 <h1>{intl.formatMessage(messages.headline, { name })}</h1>
196 <div className="auth__letter">
197 <p>
198 {intl.formatMessage(messages.specialTreat)}
199 <br />
200 </p>
201 <p>
202 {intl.formatMessage(messages.tryPro)}
203 <br />
204 </p>
205 <p>
206 {intl.formatMessage(messages.happyMessaging)}
207 </p>
208 <p>
209 <strong>Stefan Malzner</strong>
210 </p>
211 </div>
212 <div className={classes.priceContainer}>
213 <p className={classnames(classes.price, classes.regularPrice)}>
214 <span className={classes.figure}>
215 {currency}
216 {intPart}
217 </span>
218 <sup>{fractionPart}</sup>
219 </p>
220 <p className={classnames(classes.price, classes.trialPrice)}>
221 <span className={classes.figure}>
222 {currency}
223 0
224 </span>
225 <sup>00</sup>
226 </p>
227 </div>
228 <div className={classes.keyTerms}>
229 <H2>
230 {intl.formatMessage(messages.noStringsAttachedHeadline)}
231 </H2>
232 <ul className={classes.keyTermsList}>
233 <FeatureItem
234 icon="👉"
235 name={intl.formatMessage(messages.trialWorth, {
236 currency,
237 price,
238 })}
239 />
240 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
241 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
242 </ul>
243 </div>
244 {trialActivationError && (
245 <p className={classes.error}>{intl.formatMessage(messages.activationError)}</p>
246 )}
247 <Button
248 label={intl.formatMessage(!canSkipTrial ? messages.ctaStart : messages.ctaAccept)}
249 className={classes.cta}
250 onClick={onSubmit}
251 busy={isActivatingTrial}
252 disabled={isLoadingRequiredData || isActivatingTrial}
253 />
254 {canSkipTrial && (
255 <p className={classes.skipLink}>
256 <a href="#/">{intl.formatMessage(messages.ctaSkip)}</a>
257 </p>
258 )}
259 </form>
260 </div>
261 <div className={classes.featureContainer}>
262 <H2>
263 {intl.formatMessage(messages.featuresHeadline)}
264 </H2>
265 <FeatureList />
266 </div>
267 </>
268 );
269 }
270}
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index a60270a6f..7e4d0e53e 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -19,10 +19,7 @@ import { isWindows } from '../../environment';
19import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator'; 19import WorkspaceSwitchingIndicator from '../../features/workspaces/components/WorkspaceSwitchingIndicator';
20import { workspaceStore } from '../../features/workspaces'; 20import { workspaceStore } from '../../features/workspaces';
21import AppUpdateInfoBar from '../AppUpdateInfoBar'; 21import AppUpdateInfoBar from '../AppUpdateInfoBar';
22import TrialActivationInfoBar from '../TrialActivationInfoBar';
23import Todos from '../../features/todos/containers/TodosScreen'; 22import Todos from '../../features/todos/containers/TodosScreen';
24import PlanSelection from '../../features/planSelection/containers/PlanSelectionScreen';
25import TrialStatusBar from '../../features/trialStatusBar/containers/TrialStatusBarScreen';
26 23
27function createMarkup(HTMLString) { 24function createMarkup(HTMLString) {
28 return { __html: HTMLString }; 25 return { __html: HTMLString };
@@ -79,7 +76,6 @@ class AppLayout extends Component {
79 areRequiredRequestsSuccessful: PropTypes.bool.isRequired, 76 areRequiredRequestsSuccessful: PropTypes.bool.isRequired,
80 retryRequiredRequests: PropTypes.func.isRequired, 77 retryRequiredRequests: PropTypes.func.isRequired,
81 areRequiredRequestsLoading: PropTypes.bool.isRequired, 78 areRequiredRequestsLoading: PropTypes.bool.isRequired,
82 hasActivatedTrial: PropTypes.bool.isRequired,
83 }; 79 };
84 80
85 state = { 81 state = {
@@ -115,7 +111,6 @@ class AppLayout extends Component {
115 areRequiredRequestsSuccessful, 111 areRequiredRequestsSuccessful,
116 retryRequiredRequests, 112 retryRequiredRequests,
117 areRequiredRequestsLoading, 113 areRequiredRequestsLoading,
118 hasActivatedTrial,
119 } = this.props; 114 } = this.props;
120 115
121 const { intl } = this.context; 116 const { intl } = this.context;
@@ -148,9 +143,6 @@ class AppLayout extends Component {
148 /> 143 />
149 </InfoBar> 144 </InfoBar>
150 ))} 145 ))}
151 {hasActivatedTrial && (
152 <TrialActivationInfoBar />
153 )}
154 {!areRequiredRequestsSuccessful && showRequiredRequestsError && ( 146 {!areRequiredRequestsSuccessful && showRequiredRequestsError && (
155 <InfoBar 147 <InfoBar
156 type="danger" 148 type="danger"
@@ -202,11 +194,9 @@ class AppLayout extends Component {
202 <PublishDebugInfo /> 194 <PublishDebugInfo />
203 {services} 195 {services}
204 {children} 196 {children}
205 <TrialStatusBar />
206 </div> 197 </div>
207 <Todos /> 198 <Todos />
208 </div> 199 </div>
209 <PlanSelection />
210 </div> 200 </div>
211 </ErrorBoundary> 201 </ErrorBoundary>
212 ); 202 );
diff --git a/src/components/services/content/ServiceRestricted.js b/src/components/services/content/ServiceRestricted.js
deleted file mode 100644
index 4b8d926aa..000000000
--- a/src/components/services/content/ServiceRestricted.js
+++ /dev/null
@@ -1,78 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { serviceLimitStore } from '../../../features/serviceLimit';
7import Button from '../../ui/Button';
8import { RESTRICTION_TYPES } from '../../../models/Service';
9
10const messages = defineMessages({
11 headlineServiceLimit: {
12 id: 'service.restrictedHandler.serviceLimit.headline',
13 defaultMessage: '!!!You have reached your service limit.',
14 },
15 textServiceLimit: {
16 id: 'service.restrictedHandler.serviceLimit.text',
17 defaultMessage: '!!!Please upgrade your account to use more than {count} services.',
18 },
19 headlineCustomUrl: {
20 id: 'service.restrictedHandler.customUrl.headline',
21 defaultMessage: '!!!Franz Professional Plan required',
22 },
23 textCustomUrl: {
24 id: 'service.restrictedHandler.customUrl.text',
25 defaultMessage: '!!!Please upgrade to the Franz Professional plan to use custom urls & self hosted services.',
26 },
27 action: {
28 id: 'service.restrictedHandler.action',
29 defaultMessage: '!!!Upgrade Account',
30 },
31});
32
33export default @observer class ServiceRestricted extends Component {
34 static propTypes = {
35 name: PropTypes.string.isRequired,
36 upgrade: PropTypes.func.isRequired,
37 type: PropTypes.number.isRequired,
38 };
39
40 static contextTypes = {
41 intl: intlShape,
42 };
43
44 countdownInterval = null;
45
46 countdownIntervalTimeout = 1000;
47
48 render() {
49 const {
50 name,
51 upgrade,
52 type,
53 } = this.props;
54 const { intl } = this.context;
55
56 return (
57 <div className="services__info-layer">
58 {type === RESTRICTION_TYPES.SERVICE_LIMIT && (
59 <>
60 <h1>{intl.formatMessage(messages.headlineServiceLimit)}</h1>
61 <p>{intl.formatMessage(messages.textServiceLimit, { count: serviceLimitStore.serviceLimit })}</p>
62 </>
63 )}
64 {type === RESTRICTION_TYPES.CUSTOM_URL && (
65 <>
66 <h1>{intl.formatMessage(messages.headlineCustomUrl)}</h1>
67 <p>{intl.formatMessage(messages.textCustomUrl)}</p>
68 </>
69 )}
70 <Button
71 label={intl.formatMessage(messages.action, { name })}
72 buttonType="inverted"
73 onClick={() => upgrade()}
74 />
75 </div>
76 );
77 }
78}
diff --git a/src/components/services/content/Services.js b/src/components/services/content/Services.js
index caa3cf9aa..6e46a60d2 100644
--- a/src/components/services/content/Services.js
+++ b/src/components/services/content/Services.js
@@ -54,7 +54,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
54 openSettings: PropTypes.func.isRequired, 54 openSettings: PropTypes.func.isRequired,
55 update: PropTypes.func.isRequired, 55 update: PropTypes.func.isRequired,
56 userHasCompletedSignup: PropTypes.bool.isRequired, 56 userHasCompletedSignup: PropTypes.bool.isRequired,
57 hasActivatedTrial: PropTypes.bool.isRequired,
58 classes: PropTypes.object.isRequired, 57 classes: PropTypes.object.isRequired,
59 actions: PropTypes.object.isRequired, 58 actions: PropTypes.object.isRequired,
60 isSpellcheckerEnabled: PropTypes.bool.isRequired, 59 isSpellcheckerEnabled: PropTypes.bool.isRequired,
@@ -109,7 +108,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
109 openSettings, 108 openSettings,
110 update, 109 update,
111 userHasCompletedSignup, 110 userHasCompletedSignup,
112 hasActivatedTrial,
113 classes, 111 classes,
114 isSpellcheckerEnabled, 112 isSpellcheckerEnabled,
115 } = this.props; 113 } = this.props;
@@ -123,7 +121,7 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
123 121
124 return ( 122 return (
125 <div className="services"> 123 <div className="services">
126 {(userHasCompletedSignup || hasActivatedTrial) && ( 124 {userHasCompletedSignup && (
127 <div className={classes.confettiContainer}> 125 <div className={classes.confettiContainer}>
128 <Confetti 126 <Confetti
129 width={window.width} 127 width={window.width}
@@ -186,7 +184,6 @@ export default @injectSheet(styles) @inject('actions') @observer class Services
186 }, 184 },
187 redirect: false, 185 redirect: false,
188 })} 186 })}
189 upgrade={() => openSettings({ path: 'user' })}
190 isSpellcheckerEnabled={isSpellcheckerEnabled} 187 isSpellcheckerEnabled={isSpellcheckerEnabled}
191 /> 188 />
192 ))} 189 ))}
diff --git a/src/components/settings/account/AccountDashboard.js b/src/components/settings/account/AccountDashboard.js
index 68d88e218..d3d75a979 100644
--- a/src/components/settings/account/AccountDashboard.js
+++ b/src/components/settings/account/AccountDashboard.js
@@ -3,14 +3,11 @@ import PropTypes from 'prop-types';
3import { observer, PropTypes as MobxPropTypes } from 'mobx-react'; 3import { observer, PropTypes as MobxPropTypes } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl'; 4import { defineMessages, intlShape } from 'react-intl';
5import ReactTooltip from 'react-tooltip'; 5import ReactTooltip from 'react-tooltip';
6import { ProBadge, H1, H2 } from '@meetfranz/ui'; 6import { H1, H2 } from '@meetfranz/ui';
7import moment from 'moment';
8 7
9import Loader from '../../ui/Loader'; 8import Loader from '../../ui/Loader';
10import Button from '../../ui/Button'; 9import Button from '../../ui/Button';
11import Infobox from '../../ui/Infobox'; 10import Infobox from '../../ui/Infobox';
12import SubscriptionForm from '../../../containers/subscription/SubscriptionFormScreen';
13import { i18nPlanName } from '../../../helpers/plan-helpers';
14import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config'; 11import { LOCAL_SERVER, LIVE_FRANZ_API } from '../../../config';
15 12
16const messages = defineMessages({ 13const messages = defineMessages({
@@ -30,18 +27,6 @@ const messages = defineMessages({
30 id: 'settings.account.manageSubscription.label', 27 id: 'settings.account.manageSubscription.label',
31 defaultMessage: '!!!Manage your subscription', 28 defaultMessage: '!!!Manage your subscription',
32 }, 29 },
33 upgradeAccountToPro: {
34 id: 'settings.account.upgradeToPro.label',
35 defaultMessage: '!!!Upgrade to Franz Professional',
36 },
37 accountTypeBasic: {
38 id: 'settings.account.accountType.basic',
39 defaultMessage: '!!!Basic Account',
40 },
41 accountTypePremium: {
42 id: 'settings.account.accountType.premium',
43 defaultMessage: '!!!Premium Supporter Account',
44 },
45 accountEditButton: { 30 accountEditButton: {
46 id: 'settings.account.account.editButton', 31 id: 'settings.account.account.editButton',
47 defaultMessage: '!!!Edit Account', 32 defaultMessage: '!!!Edit Account',
@@ -76,23 +61,10 @@ const messages = defineMessages({
76 defaultMessage: 61 defaultMessage:
77 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!', 62 '!!!You have received an email with a link to confirm your account deletion. Your account and data cannot be restored!',
78 }, 63 },
79 trial: {
80 id: 'settings.account.trial',
81 defaultMessage: '!!!Free Trial',
82 },
83 yourLicense: { 64 yourLicense: {
84 id: 'settings.account.yourLicense', 65 id: 'settings.account.yourLicense',
85 defaultMessage: '!!!Your Franz License:', 66 defaultMessage: '!!!Your Franz License:',
86 }, 67 },
87 trialEndsIn: {
88 id: 'settings.account.trialEndsIn',
89 defaultMessage: '!!!Your free trial ends in {duration}.',
90 },
91 trialUpdateBillingInformation: {
92 id: 'settings.account.trialUpdateBillingInfo',
93 defaultMessage:
94 '!!!Please update your billing info to continue using {license} after your trial period.',
95 },
96 accountUnavailable: { 68 accountUnavailable: {
97 id: 'settings.account.accountUnavailable', 69 id: 'settings.account.accountUnavailable',
98 defaultMessage: 'Account is unavailable', 70 defaultMessage: 'Account is unavailable',
@@ -107,8 +79,6 @@ const messages = defineMessages({
107class AccountDashboard extends Component { 79class AccountDashboard extends Component {
108 static propTypes = { 80 static propTypes = {
109 user: MobxPropTypes.observableObject.isRequired, 81 user: MobxPropTypes.observableObject.isRequired,
110 isPremiumOverrideUser: PropTypes.bool.isRequired,
111 isProUser: PropTypes.bool.isRequired,
112 isLoading: PropTypes.bool.isRequired, 82 isLoading: PropTypes.bool.isRequired,
113 userInfoRequestFailed: PropTypes.bool.isRequired, 83 userInfoRequestFailed: PropTypes.bool.isRequired,
114 retryUserInfoRequest: PropTypes.func.isRequired, 84 retryUserInfoRequest: PropTypes.func.isRequired,
@@ -116,10 +86,7 @@ class AccountDashboard extends Component {
116 isLoadingDeleteAccount: PropTypes.bool.isRequired, 86 isLoadingDeleteAccount: PropTypes.bool.isRequired,
117 isDeleteAccountSuccessful: PropTypes.bool.isRequired, 87 isDeleteAccountSuccessful: PropTypes.bool.isRequired,
118 openEditAccount: PropTypes.func.isRequired, 88 openEditAccount: PropTypes.func.isRequired,
119 openBilling: PropTypes.func.isRequired,
120 upgradeToPro: PropTypes.func.isRequired,
121 openInvoices: PropTypes.func.isRequired, 89 openInvoices: PropTypes.func.isRequired,
122 onCloseSubscriptionWindow: PropTypes.func.isRequired,
123 server: PropTypes.string.isRequired, 90 server: PropTypes.string.isRequired,
124 }; 91 };
125 92
@@ -130,8 +97,6 @@ class AccountDashboard extends Component {
130 render() { 97 render() {
131 const { 98 const {
132 user, 99 user,
133 isPremiumOverrideUser,
134 isProUser,
135 isLoading, 100 isLoading,
136 userInfoRequestFailed, 101 userInfoRequestFailed,
137 retryUserInfoRequest, 102 retryUserInfoRequest,
@@ -139,20 +104,11 @@ class AccountDashboard extends Component {
139 isLoadingDeleteAccount, 104 isLoadingDeleteAccount,
140 isDeleteAccountSuccessful, 105 isDeleteAccountSuccessful,
141 openEditAccount, 106 openEditAccount,
142 openBilling,
143 upgradeToPro,
144 openInvoices, 107 openInvoices,
145 onCloseSubscriptionWindow,
146 server, 108 server,
147 } = this.props; 109 } = this.props;
148 const { intl } = this.context; 110 const { intl } = this.context;
149 111
150 let planName = '';
151
152 if (user.team && user.team.plan) {
153 planName = i18nPlanName(user.team.plan, intl);
154 }
155
156 const isUsingWithoutAccount = server === LOCAL_SERVER; 112 const isUsingWithoutAccount = server === LOCAL_SERVER;
157 const isUsingFranzServer = server === LIVE_FRANZ_API; 113 const isUsingFranzServer = server === LIVE_FRANZ_API;
158 114
@@ -210,98 +166,40 @@ class AccountDashboard extends Component {
210 <div className="account__info"> 166 <div className="account__info">
211 <H1> 167 <H1>
212 <span className="username">{`${user.firstname} ${user.lastname}`}</span> 168 <span className="username">{`${user.firstname} ${user.lastname}`}</span>
213 {user.isPremium && (
214 <>
215 {' '}
216 <ProBadge />
217 </>
218 )}
219 </H1> 169 </H1>
220 <p> 170 <p>
221 {user.organization && `${user.organization}, `} 171 {user.organization && `${user.organization}, `}
222 {user.email} 172 {user.email}
223 </p> 173 </p>
224 {user.isPremium && ( 174 <div className="manage-user-links">
225 <div className="manage-user-links"> 175 <Button
226 <Button 176 label={intl.formatMessage(
227 label={intl.formatMessage( 177 messages.accountEditButton,
228 messages.accountEditButton, 178 )}
229 )} 179 className="franz-form__button--inverted"
230 className="franz-form__button--inverted" 180 onClick={openEditAccount}
231 onClick={openEditAccount} 181 />
232 /> 182 </div>
233 </div>
234 )}
235 </div> 183 </div>
236 {!user.isPremium && ( 184 <Button
237 <Button 185 label={intl.formatMessage(
238 label={intl.formatMessage( 186 messages.accountEditButton,
239 messages.accountEditButton, 187 )}
240 )} 188 className="franz-form__button--inverted"
241 className="franz-form__button--inverted" 189 onClick={openEditAccount}
242 onClick={openEditAccount} 190 />
243 />
244 )}
245 </div> 191 </div>
246 </div> 192 </div>
247 {user.isPremium && user.isSubscriptionOwner && isUsingFranzServer && ( 193 {user.isSubscriptionOwner && isUsingFranzServer && (
248 <div className="account"> 194 <div className="account">
249 <div className="account__box"> 195 <div className="account__box">
250 <H2>{intl.formatMessage(messages.yourLicense)}</H2> 196 <H2>{intl.formatMessage(messages.yourLicense)}</H2>
251 <p> 197 <p>
252 Franz 198 Franz
253 {' '}
254 {isPremiumOverrideUser ? 'Premium' : planName}
255 {user.team.isTrial && (
256 <>
257 {' – '}
258 {intl.formatMessage(messages.trial)}
259 </>
260 )}
261 </p> 199 </p>
262 {user.team.isTrial && (
263 <>
264 <br />
265 <p>
266 {intl.formatMessage(messages.trialEndsIn, {
267 duration: moment
268 .duration(
269 moment().diff(user.team.trialEnd),
270 )
271 .humanize(),
272 })}
273 </p>
274 <p>
275 {intl.formatMessage(
276 messages.trialUpdateBillingInformation,
277 {
278 license: planName,
279 },
280 )}
281 </p>
282 </>
283 )}
284 {!isProUser && (
285 <div className="manage-user-links">
286 <Button
287 label={intl.formatMessage(
288 messages.upgradeAccountToPro,
289 )}
290 className="franz-form__button--primary"
291 onClick={upgradeToPro}
292 />
293 </div>
294 )}
295 <div className="manage-user-links"> 200 <div className="manage-user-links">
296 <Button 201 <Button
297 label={intl.formatMessage( 202 label={intl.formatMessage(
298 messages.manageSubscriptionButtonLabel,
299 )}
300 className="franz-form__button--inverted"
301 onClick={openBilling}
302 />
303 <Button
304 label={intl.formatMessage(
305 messages.invoicesButton, 203 messages.invoicesButton,
306 )} 204 )}
307 className="franz-form__button--inverted" 205 className="franz-form__button--inverted"
@@ -311,15 +209,6 @@ class AccountDashboard extends Component {
311 </div> 209 </div>
312 </div> 210 </div>
313 )} 211 )}
314 {!user.isPremium && (
315 <div className="account franz-form">
316 <div className="account__box">
317 <SubscriptionForm
318 onCloseWindow={onCloseSubscriptionWindow}
319 />
320 </div>
321 </div>
322 )}
323 </> 212 </>
324 )} 213 )}
325 214
diff --git a/src/components/settings/navigation/SettingsNavigation.js b/src/components/settings/navigation/SettingsNavigation.js
index cebab2f12..02cae6b69 100644
--- a/src/components/settings/navigation/SettingsNavigation.js
+++ b/src/components/settings/navigation/SettingsNavigation.js
@@ -2,7 +2,6 @@ import React, { Component } from 'react';
2import PropTypes from 'prop-types'; 2import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4import { inject, observer } from 'mobx-react'; 4import { inject, observer } from 'mobx-react';
5import { ProBadge } from '@meetfranz/ui';
6import { RouterStore } from 'mobx-react-router'; 5import { RouterStore } from 'mobx-react-router';
7 6
8import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config'; 7import { LOCAL_SERVER, LIVE_FERDI_API, LIVE_FRANZ_API } from '../../../config';
@@ -11,7 +10,6 @@ import { workspaceStore } from '../../../features/workspaces';
11import UIStore from '../../../stores/UIStore'; 10import UIStore from '../../../stores/UIStore';
12import SettingsStore from '../../../stores/SettingsStore'; 11import SettingsStore from '../../../stores/SettingsStore';
13import UserStore from '../../../stores/UserStore'; 12import UserStore from '../../../stores/UserStore';
14import { serviceLimitStore } from '../../../features/serviceLimit';
15 13
16const messages = defineMessages({ 14const messages = defineMessages({
17 availableServices: { 15 availableServices: {
@@ -98,8 +96,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
98 96
99 render() { 97 render() {
100 const { serviceCount, workspaceCount, stores } = this.props; 98 const { serviceCount, workspaceCount, stores } = this.props;
101 const { isDarkThemeActive } = stores.ui;
102 const { router, user } = stores;
103 const { intl } = this.context; 99 const { intl } = this.context;
104 const isLoggedIn = Boolean(localStorage.getItem('authToken')); 100 const isLoggedIn = Boolean(localStorage.getItem('authToken'));
105 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER; 101 const isUsingWithoutAccount = stores.settings.app.server === LOCAL_SERVER;
@@ -124,9 +120,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
124 {' '} 120 {' '}
125 <span className="badge"> 121 <span className="badge">
126 {serviceCount} 122 {serviceCount}
127 {serviceLimitStore.serviceLimit !== 0 && (
128 `/${serviceLimitStore.serviceLimit}`
129 )}
130 </span> 123 </span>
131 </Link> 124 </Link>
132 {workspaceStore.isFeatureEnabled ? ( 125 {workspaceStore.isFeatureEnabled ? (
@@ -138,11 +131,7 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
138 > 131 >
139 {intl.formatMessage(messages.yourWorkspaces)} 132 {intl.formatMessage(messages.yourWorkspaces)}
140 {' '} 133 {' '}
141 {workspaceStore.isPremiumUpgradeRequired ? ( 134 <span className="badge">{workspaceCount}</span>
142 <ProBadge inverted={!isDarkThemeActive && workspaceStore.isSettingsRouteActive} />
143 ) : (
144 <span className="badge">{workspaceCount}</span>
145 )}
146 </Link> 135 </Link>
147 ) : null} 136 ) : null}
148 {!isUsingWithoutAccount && ( 137 {!isUsingWithoutAccount && (
@@ -163,9 +152,6 @@ export default @inject('stores', 'actions') @observer class SettingsNavigation e
163 disabled={!isLoggedIn} 152 disabled={!isLoggedIn}
164 > 153 >
165 {intl.formatMessage(messages.team)} 154 {intl.formatMessage(messages.team)}
166 {!user.data.isPremium && (
167 <ProBadge inverted={!isDarkThemeActive && router.location.pathname === '/settings/team'} />
168 )}
169 </Link> 155 </Link>
170 )} 156 )}
171 <Link 157 <Link
diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js
index b4e2fc05c..e82324cff 100644
--- a/src/components/settings/recipes/RecipesDashboard.js
+++ b/src/components/settings/recipes/RecipesDashboard.js
@@ -13,8 +13,6 @@ import RecipeItem from './RecipeItem';
13import Loader from '../../ui/Loader'; 13import Loader from '../../ui/Loader';
14import Appear from '../../ui/effects/Appear'; 14import Appear from '../../ui/effects/Appear';
15import { FRANZ_SERVICE_REQUEST } from '../../../config'; 15import { FRANZ_SERVICE_REQUEST } from '../../../config';
16import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
17import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
18import RecipePreview from '../../../models/RecipePreview'; 16import RecipePreview from '../../../models/RecipePreview';
19 17
20const messages = defineMessages({ 18const messages = defineMessages({
@@ -120,7 +118,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
120 openRecipeDirectory: PropTypes.func.isRequired, 118 openRecipeDirectory: PropTypes.func.isRequired,
121 openDevDocs: PropTypes.func.isRequired, 119 openDevDocs: PropTypes.func.isRequired,
122 classes: PropTypes.object.isRequired, 120 classes: PropTypes.object.isRequired,
123 isCommunityRecipesIncludedInCurrentPlan: PropTypes.bool.isRequired,
124 }; 121 };
125 122
126 static defaultProps = { 123 static defaultProps = {
@@ -148,7 +145,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
148 openRecipeDirectory, 145 openRecipeDirectory,
149 openDevDocs, 146 openDevDocs,
150 classes, 147 classes,
151 isCommunityRecipesIncludedInCurrentPlan,
152 } = this.props; 148 } = this.props;
153 const { intl } = this.context; 149 const { intl } = this.context;
154 150
@@ -163,7 +159,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
163 <div className="settings__header"> 159 <div className="settings__header">
164 <h1>{intl.formatMessage(messages.headline)}</h1> 160 <h1>{intl.formatMessage(messages.headline)}</h1>
165 </div> 161 </div>
166 <LimitReachedInfobox />
167 <div className="settings__body recipes"> 162 <div className="settings__body recipes">
168 {serviceStatus.length > 0 && serviceStatus.includes('created') && ( 163 {serviceStatus.length > 0 && serviceStatus.includes('created') && (
169 <Appear> 164 <Appear>
@@ -223,9 +218,6 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
223 <> 218 <>
224 <H2> 219 <H2>
225 {intl.formatMessage(messages.headlineCustomRecipes)} 220 {intl.formatMessage(messages.headlineCustomRecipes)}
226 {!isCommunityRecipesIncludedInCurrentPlan && (
227 <ProBadge className={classes.proBadge} />
228 )}
229 </H2> 221 </H2>
230 <div className={classes.devRecipeIntroContainer}> 222 <div className={classes.devRecipeIntroContainer}>
231 <p> 223 <p>
@@ -251,37 +243,33 @@ export default @injectSheet(styles) @observer class RecipesDashboard extends Com
251 </div> 243 </div>
252 </> 244 </>
253 )} 245 )}
254 <PremiumFeatureContainer 246 {recipeFilter === 'dev' && communityRecipes.length > 0 && (
255 condition={(recipeFilter === 'dev' && communityRecipes.length > 0) && !isCommunityRecipesIncludedInCurrentPlan} 247 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3>
256 > 248 )}
257 {recipeFilter === 'dev' && communityRecipes.length > 0 && ( 249 <div className="recipes__list">
258 <H3>{intl.formatMessage(messages.headlineCommunityRecipes)}</H3> 250 {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && (
259 )} 251 <div className="align-middle settings__empty-state">
260 <div className="recipes__list"> 252 <span className="emoji">
261 {hasLoadedRecipes && recipes.length === 0 && recipeFilter !== 'dev' && ( 253 <img src="./assets/images/emoji/dontknow.png" alt="" />
262 <div className="align-middle settings__empty-state"> 254 </span>
263 <span className="emoji">
264 <img src="./assets/images/emoji/dontknow.png" alt="" />
265 </span>
266 255
267 <p className="settings__empty-state-text">{intl.formatMessage(messages.nothingFound)}</p> 256 <p className="settings__empty-state-text">{intl.formatMessage(messages.nothingFound)}</p>
268 257
269 <RecipeItem
270 key={customWebsiteRecipe.id}
271 recipe={customWebsiteRecipe}
272 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: customWebsiteRecipe.id })}
273 />
274 </div>
275 )}
276 {communityRecipes.map(recipe => (
277 <RecipeItem 258 <RecipeItem
278 key={recipe.id} 259 key={customWebsiteRecipe.id}
279 recipe={recipe} 260 recipe={customWebsiteRecipe}
280 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })} 261 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: customWebsiteRecipe.id })}
281 /> 262 />
282 ))} 263 </div>
283 </div> 264 )}
284 </PremiumFeatureContainer> 265 {communityRecipes.map(recipe => (
266 <RecipeItem
267 key={recipe.id}
268 recipe={recipe}
269 onClick={() => isLoggedIn && showAddServiceInterface({ recipeId: recipe.id })}
270 />
271 ))}
272 </div>
285 {recipeFilter === 'dev' && devRecipes.length > 0 && ( 273 {recipeFilter === 'dev' && devRecipes.length > 0 && (
286 <div className={classes.devRecipeList}> 274 <div className={classes.devRecipeList}>
287 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3> 275 <H3>{intl.formatMessage(messages.headlineDevRecipes)}</H3>
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js
index 513c75eed..0f7c29de5 100644
--- a/src/components/settings/services/EditServiceForm.js
+++ b/src/components/settings/services/EditServiceForm.js
@@ -6,7 +6,6 @@ import { defineMessages, intlShape } from 'react-intl';
6import normalizeUrl from 'normalize-url'; 6import normalizeUrl from 'normalize-url';
7 7
8import Form from '../../../lib/Form'; 8import Form from '../../../lib/Form';
9import User from '../../../models/User';
10import Recipe from '../../../models/Recipe'; 9import Recipe from '../../../models/Recipe';
11import Service from '../../../models/Service'; 10import Service from '../../../models/Service';
12import Tabs, { TabItem } from '../../ui/Tabs'; 11import Tabs, { TabItem } from '../../ui/Tabs';
@@ -17,9 +16,6 @@ import Button from '../../ui/Button';
17import ImageUpload from '../../ui/ImageUpload'; 16import ImageUpload from '../../ui/ImageUpload';
18import Select from '../../ui/Select'; 17import Select from '../../ui/Select';
19 18
20import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
21import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
22import { serviceLimitStore } from '../../../features/serviceLimit';
23import { isMac } from '../../../environment'; 19import { isMac } from '../../../environment';
24import globalMessages from '../../../i18n/globalMessages'; 20import globalMessages from '../../../i18n/globalMessages';
25 21
@@ -80,14 +76,6 @@ const messages = defineMessages({
80 id: 'settings.service.form.customUrlValidationError', 76 id: 'settings.service.form.customUrlValidationError',
81 defaultMessage: '!!!Could not validate custom {name} server.', 77 defaultMessage: '!!!Could not validate custom {name} server.',
82 }, 78 },
83 customUrlPremiumInfo: {
84 id: 'settings.service.form.customUrlPremiumInfo',
85 defaultMessage: '!!!To add self hosted services, you need a Ferdi Premium Supporter Account.',
86 },
87 customUrlUpgradeAccount: {
88 id: 'settings.service.form.customUrlUpgradeAccount',
89 defaultMessage: '!!!Upgrade your account',
90 },
91 indirectMessageInfo: { 79 indirectMessageInfo: {
92 id: 'settings.service.form.indirectMessageInfo', 80 id: 'settings.service.form.indirectMessageInfo',
93 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...', 81 defaultMessage: '!!!You will be notified about all new messages in a channel, not just @username, @channel, @here, ...',
@@ -149,7 +137,6 @@ export default @observer class EditServiceForm extends Component {
149 137
150 return null; 138 return null;
151 }, 139 },
152 user: PropTypes.instanceOf(User).isRequired,
153 action: PropTypes.string.isRequired, 140 action: PropTypes.string.isRequired,
154 form: PropTypes.instanceOf(Form).isRequired, 141 form: PropTypes.instanceOf(Form).isRequired,
155 onSubmit: PropTypes.func.isRequired, 142 onSubmit: PropTypes.func.isRequired,
@@ -158,8 +145,6 @@ export default @observer class EditServiceForm extends Component {
158 isSaving: PropTypes.bool.isRequired, 145 isSaving: PropTypes.bool.isRequired,
159 isDeleting: PropTypes.bool.isRequired, 146 isDeleting: PropTypes.bool.isRequired,
160 isProxyFeatureEnabled: PropTypes.bool.isRequired, 147 isProxyFeatureEnabled: PropTypes.bool.isRequired,
161 isServiceProxyIncludedInCurrentPlan: PropTypes.bool.isRequired,
162 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
163 isHibernationFeatureActive: PropTypes.bool.isRequired, 148 isHibernationFeatureActive: PropTypes.bool.isRequired,
164 }; 149 };
165 150
@@ -217,15 +202,12 @@ export default @observer class EditServiceForm extends Component {
217 recipe, 202 recipe,
218 service, 203 service,
219 action, 204 action,
220 user,
221 form, 205 form,
222 isSaving, 206 isSaving,
223 isDeleting, 207 isDeleting,
224 onDelete, 208 onDelete,
225 openRecipeFile, 209 openRecipeFile,
226 isProxyFeatureEnabled, 210 isProxyFeatureEnabled,
227 isServiceProxyIncludedInCurrentPlan,
228 isSpellcheckerIncludedInCurrentPlan,
229 isHibernationFeatureActive, 211 isHibernationFeatureActive,
230 } = this.props; 212 } = this.props;
231 const { intl } = this.context; 213 const { intl } = this.context;
@@ -285,7 +267,6 @@ export default @observer class EditServiceForm extends Component {
285 )} 267 )}
286 </span> 268 </span>
287 </div> 269 </div>
288 <LimitReachedInfobox />
289 <div className="settings__body"> 270 <div className="settings__body">
290 <form onSubmit={e => this.submit(e)} id="form"> 271 <form onSubmit={e => this.submit(e)} id="form">
291 <div className="service-name"> 272 <div className="service-name">
@@ -311,24 +292,11 @@ export default @observer class EditServiceForm extends Component {
311 )} 292 )}
312 {recipe.hasCustomUrl && ( 293 {recipe.hasCustomUrl && (
313 <TabItem title={intl.formatMessage(messages.tabOnPremise)}> 294 <TabItem title={intl.formatMessage(messages.tabOnPremise)}>
314 {user.isPremium || recipe.author.find(a => a.email === user.email) ? ( 295 <Input field={form.$('customUrl')} />
315 <> 296 {form.error === 'url-validation-error' && (
316 <Input field={form.$('customUrl')} /> 297 <p className="franz-form__error">
317 {form.error === 'url-validation-error' && ( 298 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
318 <p className="franz-form__error"> 299 </p>
319 {intl.formatMessage(messages.customUrlValidationError, { name: recipe.name })}
320 </p>
321 )}
322 </>
323 ) : (
324 <div className="center premium-info">
325 <p>{intl.formatMessage(messages.customUrlPremiumInfo)}</p>
326 <p>
327 <Link to="/settings/user" className="button">
328 {intl.formatMessage(messages.customUrlUpgradeAccount)}
329 </Link>
330 </p>
331 </div>
332 )} 300 )}
333 </TabItem> 301 </TabItem>
334 )} 302 )}
@@ -403,56 +371,46 @@ export default @observer class EditServiceForm extends Component {
403 </div> 371 </div>
404 372
405 {!isMac && ( 373 {!isMac && (
406 <PremiumFeatureContainer 374 <div className="settings__settings-group">
407 condition={!isSpellcheckerIncludedInCurrentPlan} 375 <Select field={form.$('spellcheckerLanguage')} />
408 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 376 </div>
409 >
410 <div className="settings__settings-group">
411 <Select field={form.$('spellcheckerLanguage')} />
412 </div>
413 </PremiumFeatureContainer>
414 )} 377 )}
415 378
416 {isProxyFeatureEnabled && ( 379 {isProxyFeatureEnabled && (
417 <PremiumFeatureContainer 380 <div className="settings__settings-group">
418 condition={!isServiceProxyIncludedInCurrentPlan} 381 <h3>
419 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'proxy' }} 382 {intl.formatMessage(messages.headlineProxy)}
420 > 383 <span className="badge badge--success">beta</span>
421 <div className="settings__settings-group"> 384 </h3>
422 <h3> 385 <Toggle field={form.$('proxy.isEnabled')} />
423 {intl.formatMessage(messages.headlineProxy)} 386 {form.$('proxy.isEnabled').value && (
424 <span className="badge badge--success">beta</span> 387 <>
425 </h3> 388 <div className="grid">
426 <Toggle field={form.$('proxy.isEnabled')} /> 389 <div className="grid__row">
427 {form.$('proxy.isEnabled').value && ( 390 <Input field={form.$('proxy.host')} className="proxyHost" />
428 <> 391 <Input field={form.$('proxy.port')} />
429 <div className="grid">
430 <div className="grid__row">
431 <Input field={form.$('proxy.host')} className="proxyHost" />
432 <Input field={form.$('proxy.port')} />
433 </div>
434 </div> 392 </div>
435 <div className="grid"> 393 </div>
436 <div className="grid__row"> 394 <div className="grid">
437 <Input field={form.$('proxy.user')} /> 395 <div className="grid__row">
438 <Input 396 <Input field={form.$('proxy.user')} />
439 field={form.$('proxy.password')} 397 <Input
440 showPasswordToggle 398 field={form.$('proxy.password')}
441 /> 399 showPasswordToggle
442 </div> 400 />
443 </div> 401 </div>
444 <p> 402 </div>
445 <span className="mdi mdi-information" /> 403 <p>
446 {intl.formatMessage(messages.proxyRestartInfo)} 404 <span className="mdi mdi-information" />
447 </p> 405 {intl.formatMessage(messages.proxyRestartInfo)}
448 <p> 406 </p>
449 <span className="mdi mdi-information" /> 407 <p>
450 {intl.formatMessage(messages.proxyInfo)} 408 <span className="mdi mdi-information" />
451 </p> 409 {intl.formatMessage(messages.proxyInfo)}
452 </> 410 </p>
453 )} 411 </>
454 </div> 412 )}
455 </PremiumFeatureContainer> 413 </div>
456 )} 414 )}
457 415
458 <div className="user-agent"> 416 <div className="user-agent">
@@ -512,7 +470,7 @@ export default @observer class EditServiceForm extends Component {
512 type="submit" 470 type="submit"
513 label={intl.formatMessage(messages.saveService)} 471 label={intl.formatMessage(messages.saveService)}
514 htmlForm="form" 472 htmlForm="form"
515 disabled={action !== 'edit' && ((form.isPristine && requiresUserInput) || serviceLimitStore.userHasReachedServiceLimit)} 473 disabled={action !== 'edit' && (form.isPristine && requiresUserInput)}
516 /> 474 />
517 )} 475 )}
518 </div> 476 </div>
diff --git a/src/components/settings/services/ServicesDashboard.js b/src/components/settings/services/ServicesDashboard.js
index a0f05fd20..a05af5da0 100644
--- a/src/components/settings/services/ServicesDashboard.js
+++ b/src/components/settings/services/ServicesDashboard.js
@@ -10,7 +10,6 @@ import Loader from '../../ui/Loader';
10import FAB from '../../ui/FAB'; 10import FAB from '../../ui/FAB';
11import ServiceItem from './ServiceItem'; 11import ServiceItem from './ServiceItem';
12import Appear from '../../ui/effects/Appear'; 12import Appear from '../../ui/effects/Appear';
13import LimitReachedInfobox from '../../../features/serviceLimit/components/LimitReachedInfobox';
14 13
15const messages = defineMessages({ 14const messages = defineMessages({
16 headline: { 15 headline: {
@@ -93,7 +92,6 @@ export default @observer class ServicesDashboard extends Component {
93 <div className="settings__header"> 92 <div className="settings__header">
94 <h1>{intl.formatMessage(messages.headline)}</h1> 93 <h1>{intl.formatMessage(messages.headline)}</h1>
95 </div> 94 </div>
96 <LimitReachedInfobox />
97 <div className="settings__body"> 95 <div className="settings__body">
98 {(services.length !== 0 || searchNeedle) && !isLoading && ( 96 {(services.length !== 0 || searchNeedle) && !isLoading && (
99 <SearchInput 97 <SearchInput
diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js
index 52b26d65b..a8ba8748d 100644
--- a/src/components/settings/settings/EditSettingsForm.js
+++ b/src/components/settings/settings/EditSettingsForm.js
@@ -10,7 +10,6 @@ import Button from '../../ui/Button';
10import Toggle from '../../ui/Toggle'; 10import Toggle from '../../ui/Toggle';
11import ToggleRaw from '../../ui/ToggleRaw'; 11import ToggleRaw from '../../ui/ToggleRaw';
12import Select from '../../ui/Select'; 12import Select from '../../ui/Select';
13import PremiumFeatureContainer from '../../ui/PremiumFeatureContainer';
14import Input from '../../ui/Input'; 13import Input from '../../ui/Input';
15 14
16import { 15import {
@@ -168,7 +167,6 @@ export default @observer class EditSettingsForm extends Component {
168 isClearingAllCache: PropTypes.bool.isRequired, 167 isClearingAllCache: PropTypes.bool.isRequired,
169 onClearAllCache: PropTypes.func.isRequired, 168 onClearAllCache: PropTypes.func.isRequired,
170 getCacheSize: PropTypes.func.isRequired, 169 getCacheSize: PropTypes.func.isRequired,
171 isSpellcheckerIncludedInCurrentPlan: PropTypes.bool.isRequired,
172 isTodosEnabled: PropTypes.bool.isRequired, 170 isTodosEnabled: PropTypes.bool.isRequired,
173 isTodosActivated: PropTypes.bool.isRequired, 171 isTodosActivated: PropTypes.bool.isRequired,
174 isWorkspaceEnabled: PropTypes.bool.isRequired, 172 isWorkspaceEnabled: PropTypes.bool.isRequired,
@@ -224,7 +222,6 @@ export default @observer class EditSettingsForm extends Component {
224 isClearingAllCache, 222 isClearingAllCache,
225 onClearAllCache, 223 onClearAllCache,
226 getCacheSize, 224 getCacheSize,
227 isSpellcheckerIncludedInCurrentPlan,
228 isTodosEnabled, 225 isTodosEnabled,
229 isWorkspaceEnabled, 226 isWorkspaceEnabled,
230 automaticUpdates, 227 automaticUpdates,
@@ -564,22 +561,16 @@ export default @observer class EditSettingsForm extends Component {
564 561
565 <Hr /> 562 <Hr />
566 563
567 <PremiumFeatureContainer 564 <Toggle
568 condition={!isSpellcheckerIncludedInCurrentPlan} 565 field={form.$('enableSpellchecking')}
569 gaEventInfo={{ category: 'User', event: 'upgrade', label: 'spellchecker' }} 566 />
570 > 567 {!isMac && form.$('enableSpellchecking').value && (
571 <> 568 <Select field={form.$('spellcheckerLanguage')} />
572 <Toggle 569 )}
573 field={form.$('enableSpellchecking')} 570 {isMac && form.$('enableSpellchecking').value && (
574 /> 571 <p>{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p>
575 {!isMac && form.$('enableSpellchecking').value && ( 572 )}
576 <Select field={form.$('spellcheckerLanguage')} /> 573
577 )}
578 {isMac && form.$('enableSpellchecking').value && (
579 <p>{intl.formatMessage(messages.spellCheckerLanguageInfo)}</p>
580 )}
581 </>
582 </PremiumFeatureContainer>
583 <a 574 <a
584 href={FRANZ_TRANSLATION} 575 href={FRANZ_TRANSLATION}
585 target="_blank" 576 target="_blank"
diff --git a/src/components/settings/team/TeamDashboard.js b/src/components/settings/team/TeamDashboard.js
index 602d6e490..437225058 100644
--- a/src/components/settings/team/TeamDashboard.js
+++ b/src/components/settings/team/TeamDashboard.js
@@ -6,12 +6,9 @@ import ReactTooltip from 'react-tooltip';
6import injectSheet from 'react-jss'; 6import injectSheet from 'react-jss';
7import classnames from 'classnames'; 7import classnames from 'classnames';
8 8
9import { Badge } from '@meetfranz/ui';
10import Loader from '../../ui/Loader'; 9import Loader from '../../ui/Loader';
11import Button from '../../ui/Button'; 10import Button from '../../ui/Button';
12import Infobox from '../../ui/Infobox'; 11import Infobox from '../../ui/Infobox';
13import globalMessages from '../../../i18n/globalMessages';
14import UpgradeButton from '../../ui/UpgradeButton';
15import { LIVE_FRANZ_API } from '../../../config'; 12import { LIVE_FRANZ_API } from '../../../config';
16 13
17const messages = defineMessages({ 14const messages = defineMessages({
@@ -35,10 +32,6 @@ const messages = defineMessages({
35 id: 'settings.team.manageAction', 32 id: 'settings.team.manageAction',
36 defaultMessage: '!!!Manage your Team on meetfranz.com', 33 defaultMessage: '!!!Manage your Team on meetfranz.com',
37 }, 34 },
38 upgradeButton: {
39 id: 'settings.team.upgradeAction',
40 defaultMessage: '!!!Upgrade your Account',
41 },
42 teamsUnavailable: { 35 teamsUnavailable: {
43 id: 'settings.team.teamsUnavailable', 36 id: 'settings.team.teamsUnavailable',
44 defaultMessage: '!!!Teams are unavailable', 37 defaultMessage: '!!!Teams are unavailable',
@@ -88,10 +81,6 @@ const styles = {
88 headlineWithSpacing: { 81 headlineWithSpacing: {
89 marginBottom: 'inherit', 82 marginBottom: 'inherit',
90 }, 83 },
91 proRequired: {
92 margin: [10, 0, 40],
93 height: 'auto',
94 },
95 buttonContainer: { 84 buttonContainer: {
96 display: 'flex', 85 display: 'flex',
97 height: 'auto', 86 height: 'auto',
@@ -105,7 +94,6 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
105 retryUserInfoRequest: PropTypes.func.isRequired, 94 retryUserInfoRequest: PropTypes.func.isRequired,
106 openTeamManagement: PropTypes.func.isRequired, 95 openTeamManagement: PropTypes.func.isRequired,
107 classes: PropTypes.object.isRequired, 96 classes: PropTypes.object.isRequired,
108 isProUser: PropTypes.bool.isRequired,
109 server: PropTypes.string.isRequired, 97 server: PropTypes.string.isRequired,
110 }; 98 };
111 99
@@ -119,7 +107,6 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
119 userInfoRequestFailed, 107 userInfoRequestFailed,
120 retryUserInfoRequest, 108 retryUserInfoRequest,
121 openTeamManagement, 109 openTeamManagement,
122 isProUser,
123 classes, 110 classes,
124 server, 111 server,
125 } = this.props; 112 } = this.props;
@@ -157,37 +144,25 @@ export default @injectSheet(styles) @observer class TeamDashboard extends Compon
157 <> 144 <>
158 <h1 className={classnames({ 145 <h1 className={classnames({
159 [classes.headline]: true, 146 [classes.headline]: true,
160 [classes.headlineWithSpacing]: isProUser, 147 [classes.headlineWithSpacing]: true,
161 })} 148 })}
162 > 149 >
163 {intl.formatMessage(messages.contentHeadline)} 150 {intl.formatMessage(messages.contentHeadline)}
164 151
165 </h1> 152 </h1>
166 {!isProUser && (
167 <Badge className={classes.proRequired}>{intl.formatMessage(globalMessages.proRequired)}</Badge>
168 )}
169 <div className={classes.container}> 153 <div className={classes.container}>
170 <div className={classes.content}> 154 <div className={classes.content}>
171 <p>{intl.formatMessage(messages.intro)}</p> 155 <p>{intl.formatMessage(messages.intro)}</p>
172 <p>{intl.formatMessage(messages.copy)}</p> 156 <p>{intl.formatMessage(messages.copy)}</p>
173 </div> 157 </div>
174 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Franz for Teams" /> 158 <img className={classes.image} src="https://cdn.franzinfra.com/announcements/assets/teams.png" alt="Ferdi for Teams" />
175 </div> 159 </div>
176 <div className={classes.buttonContainer}> 160 <div className={classes.buttonContainer}>
177 {!isProUser ? ( 161 <Button
178 <UpgradeButton 162 label={intl.formatMessage(messages.manageButton)}
179 className={classes.cta} 163 onClick={openTeamManagement}
180 gaEventInfo={{ category: 'Todos', event: 'upgrade' }} 164 className={classes.cta}
181 requiresPro 165 />
182 short
183 />
184 ) : (
185 <Button
186 label={intl.formatMessage(messages.manageButton)}
187 onClick={openTeamManagement}
188 className={classes.cta}
189 />
190 )}
191 </div> 166 </div>
192 </> 167 </>
193 </> 168 </>
diff --git a/src/components/subscription/SubscriptionForm.js b/src/components/subscription/SubscriptionForm.js
deleted file mode 100644
index ec486e5d0..000000000
--- a/src/components/subscription/SubscriptionForm.js
+++ /dev/null
@@ -1,78 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import { H3, H2 } from '@meetfranz/ui';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
11
12const messages = defineMessages({
13 submitButtonLabel: {
14 id: 'subscription.cta.choosePlan',
15 defaultMessage: '!!!Choose your plan',
16 },
17 teaserHeadline: {
18 id: 'settings.account.headlineUpgradeAccount',
19 defaultMessage: '!!!Upgrade your account and get the full Franz experience',
20 },
21 teaserText: {
22 id: 'subscription.teaser.intro',
23 defaultMessage: '!!!Franz 5 comes with a wide range of new features to boost up your everyday communication - batteries included. Check out our new plans and find out which one suits you most!',
24 },
25 includedFeatures: {
26 id: 'subscription.teaser.includedFeatures',
27 defaultMessage: '!!!Paid Franz Plans include:',
28 },
29});
30
31const styles = () => ({
32 activateTrialButton: {
33 margin: [40, 'auto', 50],
34 display: 'flex',
35 },
36});
37
38export default @injectSheet(styles) @observer class SubscriptionForm extends Component {
39 static propTypes = {
40 selectPlan: PropTypes.func.isRequired,
41 isActivatingTrial: PropTypes.bool.isRequired,
42 classes: PropTypes.object.isRequired,
43 };
44
45 static contextTypes = {
46 intl: intlShape,
47 };
48
49 render() {
50 const {
51 isActivatingTrial,
52 selectPlan,
53 classes,
54 } = this.props;
55 const { intl } = this.context;
56
57 return (
58 <>
59 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
60 <p>{intl.formatMessage(messages.teaserText)}</p>
61 <Button
62 label={intl.formatMessage(messages.submitButtonLabel)}
63 className={classes.activateTrialButton}
64 busy={isActivatingTrial}
65 onClick={selectPlan}
66 />
67 <div className="subscription__premium-info">
68 <H3>
69 {intl.formatMessage(messages.includedFeatures)}
70 </H3>
71 <div className="subscription">
72 <FeatureList />
73 </div>
74 </div>
75 </>
76 );
77 }
78}
diff --git a/src/components/subscription/SubscriptionPopup.js b/src/components/subscription/SubscriptionPopup.js
deleted file mode 100644
index 0df43fd4b..000000000
--- a/src/components/subscription/SubscriptionPopup.js
+++ /dev/null
@@ -1,84 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import Webview from 'react-electron-web-view';
6import ms from 'ms';
7
8import Button from '../ui/Button';
9
10const messages = defineMessages({
11 buttonCancel: {
12 id: 'subscriptionPopup.buttonCancel',
13 defaultMessage: '!!!Cancel',
14 },
15 buttonDone: {
16 id: 'subscriptionPopup.buttonDone',
17 defaultMessage: '!!!Done',
18 },
19});
20
21export default @observer class SubscriptionPopup extends Component {
22 static propTypes = {
23 url: PropTypes.string.isRequired,
24 closeWindow: PropTypes.func.isRequired,
25 completeCheck: PropTypes.func.isRequired,
26 isCompleted: PropTypes.bool.isRequired,
27 };
28
29 static contextTypes = {
30 intl: intlShape,
31 };
32
33 state = {
34 isFakeLoading: false,
35 };
36
37 // We delay the window closing a bit in order to give
38 // the Recurly webhook a few seconds to do it's magic
39 delayedCloseWindow() {
40 this.setState({
41 isFakeLoading: true,
42 });
43
44 setTimeout(() => {
45 this.props.closeWindow();
46 }, ms('1s'));
47 }
48
49 render() {
50 const {
51 url, closeWindow, completeCheck, isCompleted,
52 } = this.props;
53 const { intl } = this.context;
54
55 return (
56 <div className="subscription-popup">
57 <div className="subscription-popup__content">
58 <Webview
59 className="subscription-popup__webview"
60 autosize
61 allowpopups
62 src={encodeURI(url)}
63 onDidNavigate={completeCheck}
64 onDidNavigateInPage={completeCheck}
65 />
66 </div>
67 <div className="subscription-popup__toolbar franz-form">
68 <Button
69 label={intl.formatMessage(messages.buttonCancel)}
70 buttonType="secondary"
71 onClick={closeWindow}
72 disabled={isCompleted}
73 />
74 <Button
75 label={intl.formatMessage(messages.buttonDone)}
76 onClick={() => this.delayedCloseWindow()}
77 disabled={!isCompleted}
78 loaded={!this.state.isFakeLoading}
79 />
80 </div>
81 </div>
82 );
83 }
84}
diff --git a/src/components/subscription/TrialForm.js b/src/components/subscription/TrialForm.js
deleted file mode 100644
index d61b779ed..000000000
--- a/src/components/subscription/TrialForm.js
+++ /dev/null
@@ -1,115 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import { H3, H2 } from '@meetfranz/ui';
8
9import { Button } from '@meetfranz/forms';
10import { FeatureList } from '../ui/FeatureList';
11import { FeatureItem } from '../ui/FeatureItem';
12
13const messages = defineMessages({
14 submitButtonLabel: {
15 id: 'subscription.cta.activateTrial',
16 defaultMessage: '!!!Yes, start the free Franz Professional trial',
17 },
18 allOptionsButton: {
19 id: 'subscription.cta.allOptions',
20 defaultMessage: '!!!See all options',
21 },
22 teaserHeadline: {
23 id: 'settings.account.headlineTrialUpgrade',
24 defaultMessage: '!!!Get the free 14 day Franz Professional Trial',
25 },
26 includedFeatures: {
27 id: 'subscription.includedProFeatures',
28 defaultMessage: '!!!The Franz Professional Plan includes:',
29 },
30 noStringsAttachedHeadline: {
31 id: 'pricing.trial.terms.headline',
32 defaultMessage: '!!!No strings attached',
33 },
34 noCreditCard: {
35 id: 'pricing.trial.terms.noCreditCard',
36 defaultMessage: '!!!No credit card required',
37 },
38 automaticTrialEnd: {
39 id: 'pricing.trial.terms.automaticTrialEnd',
40 defaultMessage: '!!!Your free trial ends automatically after 14 days',
41 },
42});
43
44const styles = theme => ({
45 activateTrialButton: {
46 margin: [40, 'auto', 10],
47 display: 'flex',
48 },
49 allOptionsButton: {
50 margin: [0, 0, 40],
51 background: 'none',
52 border: 'none',
53 color: theme.colorText,
54 },
55 keyTerms: {
56 marginTop: 20,
57 },
58});
59
60export default @injectSheet(styles) @observer class TrialForm extends Component {
61 static propTypes = {
62 activateTrial: PropTypes.func.isRequired,
63 isActivatingTrial: PropTypes.bool.isRequired,
64 showAllOptions: PropTypes.func.isRequired,
65 classes: PropTypes.object.isRequired,
66 };
67
68 static contextTypes = {
69 intl: intlShape,
70 };
71
72 render() {
73 const {
74 isActivatingTrial,
75 activateTrial,
76 showAllOptions,
77 classes,
78 } = this.props;
79 const { intl } = this.context;
80
81 return (
82 <>
83 <H2>{intl.formatMessage(messages.teaserHeadline)}</H2>
84 <H3 className={classes.keyTerms}>
85 {intl.formatMessage(messages.noStringsAttachedHeadline)}
86 </H3>
87 <ul>
88 <FeatureItem icon="👉" name={intl.formatMessage(messages.noCreditCard)} />
89 <FeatureItem icon="👉" name={intl.formatMessage(messages.automaticTrialEnd)} />
90 </ul>
91
92 <Button
93 label={intl.formatMessage(messages.submitButtonLabel)}
94 className={classes.activateTrialButton}
95 busy={isActivatingTrial}
96 onClick={activateTrial}
97 />
98 <Button
99 label={intl.formatMessage(messages.allOptionsButton)}
100 className={classes.allOptionsButton}
101 onClick={showAllOptions}
102 stretch
103 />
104 <div className="subscription__premium-info">
105 <H3>
106 {intl.formatMessage(messages.includedFeatures)}
107 </H3>
108 <div className="subscription">
109 <FeatureList />
110 </div>
111 </div>
112 </>
113 );
114 }
115}
diff --git a/src/components/ui/ActivateTrialButton/index.js b/src/components/ui/ActivateTrialButton/index.js
deleted file mode 100644
index 8f4d21f64..000000000
--- a/src/components/ui/ActivateTrialButton/index.js
+++ /dev/null
@@ -1,107 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5import classnames from 'classnames';
6
7import { Button } from '@meetfranz/forms';
8
9import UserStore from '../../../stores/UserStore';
10import UIStore from '../../../stores/UIStore';
11
12const messages = defineMessages({
13 action: {
14 id: 'feature.delayApp.upgrade.action',
15 defaultMessage: '!!!Get a Franz Supporter License',
16 },
17 actionTrial: {
18 id: 'feature.delayApp.trial.action',
19 defaultMessage: '!!!Yes, I want the free 14 day trial of Franz Professional',
20 },
21 shortAction: {
22 id: 'feature.delayApp.upgrade.actionShort',
23 defaultMessage: '!!!Upgrade account',
24 },
25 shortActionTrial: {
26 id: 'feature.delayApp.trial.actionShort',
27 defaultMessage: '!!!Activate the free Franz Professional trial',
28 },
29 noStringsAttachedHeadline: {
30 id: 'pricing.trial.terms.headline',
31 defaultMessage: '!!!No strings attached',
32 },
33 noCreditCard: {
34 id: 'pricing.trial.terms.noCreditCard',
35 defaultMessage: '!!!No credit card required',
36 },
37 automaticTrialEnd: {
38 id: 'pricing.trial.terms.automaticTrialEnd',
39 defaultMessage: '!!!Your free trial ends automatically after 14 days',
40 },
41});
42
43@inject('stores', 'actions') @observer
44class ActivateTrialButton extends Component {
45 static propTypes = {
46 className: PropTypes.string,
47 short: PropTypes.bool,
48 gaEventInfo: PropTypes.shape({
49 category: PropTypes.string.isRequired,
50 event: PropTypes.string.isRequired,
51 label: PropTypes.string,
52 }),
53 };
54
55 static defaultProps = {
56 className: '',
57 short: false,
58 gaEventInfo: null,
59 }
60
61 static contextTypes = {
62 intl: intlShape,
63 };
64
65 handleCTAClick() {
66 const { actions } = this.props;
67
68 actions.ui.openSettings({ path: 'user' });
69 }
70
71 render() {
72 const { stores, className, short } = this.props;
73 const { intl } = this.context;
74
75 const { hadSubscription } = stores.user.data;
76
77 let label;
78 if (hadSubscription) {
79 label = short ? messages.shortAction : messages.action;
80 } else {
81 label = short ? messages.shortActionTrial : messages.actionTrial;
82 }
83
84 return (
85 <Button
86 label={intl.formatMessage(label)}
87 className={classnames({
88 [className]: className,
89 })}
90 buttonType="inverted"
91 onClick={this.handleCTAClick.bind(this)}
92 busy={stores.user.activateTrialRequest.isExecuting}
93 />
94 );
95 }
96}
97
98export default ActivateTrialButton;
99
100ActivateTrialButton.wrappedComponent.propTypes = {
101 stores: PropTypes.shape({
102 user: PropTypes.instanceOf(UserStore).isRequired,
103 }).isRequired,
104 actions: PropTypes.shape({
105 ui: PropTypes.instanceOf(UIStore).isRequired,
106 }).isRequired,
107};
diff --git a/src/components/ui/FeatureList.js b/src/components/ui/FeatureList.js
index 72c799819..ada15244b 100644
--- a/src/components/ui/FeatureList.js
+++ b/src/components/ui/FeatureList.js
@@ -3,12 +3,11 @@ import PropTypes from 'prop-types';
3import { defineMessages, intlShape } from 'react-intl'; 3import { defineMessages, intlShape } from 'react-intl';
4 4
5import { FeatureItem } from './FeatureItem'; 5import { FeatureItem } from './FeatureItem';
6import { PLANS } from '../../config';
7 6
8const messages = defineMessages({ 7const messages = defineMessages({
9 availableRecipes: { 8 availableRecipes: {
10 id: 'pricing.features.recipes', 9 id: 'pricing.features.recipes',
11 defaultMessage: '!!!Choose from more than 70 Services', 10 defaultMessage: '!!!Choose from more than 70 Services', // TODO: Make this dynamic
12 }, 11 },
13 accountSync: { 12 accountSync: {
14 id: 'pricing.features.accountSync', 13 id: 'pricing.features.accountSync',
@@ -22,14 +21,6 @@ const messages = defineMessages({
22 id: 'pricing.features.unlimitedServices', 21 id: 'pricing.features.unlimitedServices',
23 defaultMessage: '!!!Add unlimited services', 22 defaultMessage: '!!!Add unlimited services',
24 }, 23 },
25 upToThreeServices: {
26 id: 'pricing.features.upToThreeServices',
27 defaultMessage: '!!!Add up to 3 services',
28 },
29 upToSixServices: {
30 id: 'pricing.features.upToSixServices',
31 defaultMessage: '!!!Add up to 6 services',
32 },
33 spellchecker: { 24 spellchecker: {
34 id: 'pricing.features.spellchecker', 25 id: 'pricing.features.spellchecker',
35 defaultMessage: '!!!Spellchecker support', 26 defaultMessage: '!!!Spellchecker support',
@@ -58,31 +49,17 @@ const messages = defineMessages({
58 id: 'pricing.features.teamManagement', 49 id: 'pricing.features.teamManagement',
59 defaultMessage: '!!!Team Management', 50 defaultMessage: '!!!Team Management',
60 }, 51 },
61 appDelays: {
62 id: 'pricing.features.appDelays',
63 defaultMessage: '!!!No Waiting Screens',
64 },
65 adFree: {
66 id: 'pricing.features.adFree',
67 defaultMessage: '!!!Forever ad-free',
68 },
69 appDelayEnabled: {
70 id: 'pricing.features.appDelaysEnabled',
71 defaultMessage: '!!!Occasional Waiting Screens',
72 },
73}); 52});
74 53
75export class FeatureList extends Component { 54export class FeatureList extends Component {
76 static propTypes = { 55 static propTypes = {
77 className: PropTypes.string, 56 className: PropTypes.string,
78 featureClassName: PropTypes.string, 57 featureClassName: PropTypes.string,
79 plan: PropTypes.oneOf(Object.keys(PLANS)),
80 }; 58 };
81 59
82 static defaultProps = { 60 static defaultProps = {
83 className: '', 61 className: '',
84 featureClassName: '', 62 featureClassName: '',
85 plan: false,
86 } 63 }
87 64
88 static contextTypes = { 65 static contextTypes = {
@@ -93,49 +70,25 @@ export class FeatureList extends Component {
93 const { 70 const {
94 className, 71 className,
95 featureClassName, 72 featureClassName,
96 plan,
97 } = this.props; 73 } = this.props;
98 const { intl } = this.context; 74 const { intl } = this.context;
99 75
100 const features = []; 76 const features = [
101 if (plan === PLANS.FREE) { 77 messages.availableRecipes,
102 features.push( 78 messages.accountSync,
103 messages.appDelayEnabled, 79 messages.desktopNotifications,
104 messages.upToThreeServices, 80
105 messages.availableRecipes, 81 messages.spellchecker,
106 messages.accountSync, 82
107 messages.desktopNotifications, 83 messages.workspaces,
108 ); 84 messages.customWebsites,
109 } else if (plan === PLANS.PERSONAL) { 85 messages.thirdPartyServices,
110 features.push( 86
111 messages.upToSixServices, 87 messages.unlimitedServices,
112 messages.spellchecker, 88 messages.onPremise,
113 messages.appDelays, 89 messages.serviceProxies,
114 messages.adFree, 90 messages.teamManagement,
115 ); 91 ];
116 } else if (plan === PLANS.PRO) {
117 features.push(
118 messages.unlimitedServices,
119 messages.workspaces,
120 messages.customWebsites,
121 // messages.onPremise,
122 messages.thirdPartyServices,
123 // messages.serviceProxies,
124 );
125 } else {
126 features.push(
127 messages.unlimitedServices,
128 messages.spellchecker,
129 messages.workspaces,
130 messages.customWebsites,
131 messages.onPremise,
132 messages.thirdPartyServices,
133 messages.serviceProxies,
134 messages.teamManagement,
135 messages.appDelays,
136 messages.adFree,
137 );
138 }
139 92
140 return ( 93 return (
141 <ul className={className}> 94 <ul className={className}>
diff --git a/src/components/ui/PremiumFeatureContainer/index.js b/src/components/ui/PremiumFeatureContainer/index.js
deleted file mode 100644
index 1e100f9d8..000000000
--- a/src/components/ui/PremiumFeatureContainer/index.js
+++ /dev/null
@@ -1,101 +0,0 @@
1import React, { Component } from 'react';
2import { inject, observer } from 'mobx-react';
3import PropTypes from 'prop-types';
4import { defineMessages, intlShape } from 'react-intl';
5import injectSheet from 'react-jss';
6
7import { oneOrManyChildElements } from '../../../prop-types';
8
9import UserStore from '../../../stores/UserStore';
10
11import styles from './styles';
12import FeaturesStore from '../../../stores/FeaturesStore';
13import UIStore from '../../../stores/UIStore';
14
15const messages = defineMessages({
16 action: {
17 id: 'premiumFeature.button.upgradeAccount',
18 defaultMessage: '!!!Upgrade account',
19 },
20});
21
22@inject('stores', 'actions') @injectSheet(styles) @observer
23class PremiumFeatureContainer extends Component {
24 static propTypes = {
25 classes: PropTypes.object.isRequired,
26 condition: PropTypes.oneOfType([
27 PropTypes.bool,
28 PropTypes.func,
29 ]),
30 gaEventInfo: PropTypes.shape({
31 category: PropTypes.string.isRequired,
32 event: PropTypes.string.isRequired,
33 label: PropTypes.string,
34 }),
35 };
36
37 static defaultProps = {
38 condition: null,
39 gaEventInfo: null,
40 };
41
42 static contextTypes = {
43 intl: intlShape,
44 };
45
46 render() {
47 const {
48 classes,
49 children,
50 actions,
51 condition,
52 stores,
53 } = this.props;
54
55 const { intl } = this.context;
56
57 let showWrapper = !!condition;
58
59 if (condition === null) {
60 showWrapper = !stores.user.data.isPremium;
61 } else if (typeof condition === 'function') {
62 showWrapper = condition({
63 isPremium: stores.user.data.isPremium,
64 features: stores.features.features,
65 });
66 }
67
68 return showWrapper ? (
69 <div className={classes.container}>
70 <div className={classes.titleContainer}>
71 <p className={classes.title}>Premium Feature</p>
72 <button
73 className={classes.actionButton}
74 type="button"
75 onClick={() => {
76 actions.ui.openSettings({ path: 'user' });
77 }}
78 >
79 {intl.formatMessage(messages.action)}
80 </button>
81 </div>
82 <div className={classes.content}>
83 {children}
84 </div>
85 </div>
86 ) : children;
87 }
88}
89
90PremiumFeatureContainer.wrappedComponent.propTypes = {
91 children: oneOrManyChildElements.isRequired,
92 stores: PropTypes.shape({
93 user: PropTypes.instanceOf(UserStore).isRequired,
94 features: PropTypes.instanceOf(FeaturesStore).isRequired,
95 }).isRequired,
96 actions: PropTypes.shape({
97 ui: PropTypes.instanceOf(UIStore).isRequired,
98 }).isRequired,
99};
100
101export default PremiumFeatureContainer;
diff --git a/src/components/ui/PremiumFeatureContainer/styles.js b/src/components/ui/PremiumFeatureContainer/styles.js
deleted file mode 100644
index 41881e044..000000000
--- a/src/components/ui/PremiumFeatureContainer/styles.js
+++ /dev/null
@@ -1,34 +0,0 @@
1export default theme => ({
2 container: {
3 background: theme.colorSubscriptionContainerBackground,
4 border: theme.colorSubscriptionContainerBorder,
5 margin: [0, 0, 20, -20],
6 padding: 20,
7 'border-radius': theme.borderRadius,
8 pointerEvents: 'none',
9 height: 'auto',
10 },
11 titleContainer: {
12 display: 'flex',
13 },
14 title: {
15 'font-weight': 'bold',
16 color: theme.colorSubscriptionContainerTitle,
17 },
18 actionButton: {
19 background: theme.colorSubscriptionContainerActionButtonBackground,
20 color: theme.colorSubscriptionContainerActionButtonColor,
21 'margin-left': 'auto',
22 'border-radius': theme.borderRadiusSmall,
23 padding: [4, 8],
24 'font-size': 12,
25 pointerEvents: 'initial',
26 },
27 content: {
28 opacity: 0.5,
29 'margin-top': 20,
30 '& > :last-child': {
31 'margin-bottom': 0,
32 },
33 },
34});
diff --git a/src/components/ui/UpgradeButton/index.js b/src/components/ui/UpgradeButton/index.js
deleted file mode 100644
index eade46cfd..000000000
--- a/src/components/ui/UpgradeButton/index.js
+++ /dev/null
@@ -1,83 +0,0 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { inject, observer } from 'mobx-react';
4import { defineMessages, intlShape } from 'react-intl';
5
6import { Button } from '@meetfranz/forms';
7
8import UserStore from '../../../stores/UserStore';
9import ActivateTrialButton from '../ActivateTrialButton';
10import UIStore from '../../../stores/UIStore';
11
12const messages = defineMessages({
13 upgradeToPro: {
14 id: 'global.upgradeButton.upgradeToPro',
15 defaultMessage: '!!!Upgrade to Franz Professional',
16 },
17});
18
19@inject('stores', 'actions') @observer
20class UpgradeButton extends Component {
21 static propTypes = {
22 // eslint-disable-next-line
23 classes: PropTypes.object.isRequired,
24 className: PropTypes.string,
25 gaEventInfo: PropTypes.shape({
26 category: PropTypes.string.isRequired,
27 event: PropTypes.string.isRequired,
28 label: PropTypes.string,
29 }),
30 requiresPro: PropTypes.bool,
31 };
32
33 static defaultProps = {
34 className: '',
35 gaEventInfo: null,
36 requiresPro: false,
37 }
38
39 static contextTypes = {
40 intl: intlShape,
41 };
42
43 handleCTAClick() {
44 const { actions } = this.props;
45
46 actions.ui.openSettings({ path: 'user' });
47 }
48
49 render() {
50 const { stores, requiresPro } = this.props;
51 const { intl } = this.context;
52
53 const { isPremium, isPersonal } = stores.user;
54
55 if (isPremium && isPersonal && requiresPro) {
56 return (
57 <Button
58 label={intl.formatMessage(messages.upgradeToPro)}
59 onClick={this.handleCTAClick.bind(this)}
60 className={this.props.className}
61 buttonType="inverted"
62 />
63 );
64 }
65
66 if (!isPremium) {
67 return <ActivateTrialButton {...this.props} />;
68 }
69
70 return null;
71 }
72}
73
74export default UpgradeButton;
75
76UpgradeButton.wrappedComponent.propTypes = {
77 stores: PropTypes.shape({
78 user: PropTypes.instanceOf(UserStore).isRequired,
79 }).isRequired,
80 actions: PropTypes.shape({
81 ui: PropTypes.instanceOf(UIStore).isRequired,
82 }).isRequired,
83};