aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2019-10-15 21:40:14 +0200
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-10-15 21:40:14 +0200
commit91a0fb20ef02dfa342cf26df3e047b2bd4370b9f (patch)
treef411b3d7d83a24b015a2a1ed723df2e2a324cc0c /src/features
parentOptimize button width (diff)
downloadferdium-app-91a0fb20ef02dfa342cf26df3e047b2bd4370b9f.tar.gz
ferdium-app-91a0fb20ef02dfa342cf26df3e047b2bd4370b9f.tar.zst
ferdium-app-91a0fb20ef02dfa342cf26df3e047b2bd4370b9f.zip
simplify plan selection
Diffstat (limited to 'src/features')
-rw-r--r--src/features/planSelection/actions.js13
-rw-r--r--src/features/planSelection/api.js26
-rw-r--r--src/features/planSelection/components/PlanItem.js186
-rw-r--r--src/features/planSelection/components/PlanSelection.js233
-rw-r--r--src/features/planSelection/containers/PlanSelectionScreen.js138
-rw-r--r--src/features/planSelection/index.js30
-rw-r--r--src/features/planSelection/store.js113
-rw-r--r--src/features/shareFranz/index.js3
8 files changed, 741 insertions, 1 deletions
diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js
new file mode 100644
index 000000000..21aa38ace
--- /dev/null
+++ b/src/features/planSelection/actions.js
@@ -0,0 +1,13 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const planSelectionActions = createActionsFromDefinitions({
5 upgradeAccount: {
6 planId: PropTypes.string.isRequired,
7 onCloseWindow: PropTypes.func.isRequired,
8 },
9 downgradeAccount: {},
10 hideOverlay: {},
11}, PropTypes.checkPropTypes);
12
13export default planSelectionActions;
diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js
new file mode 100644
index 000000000..734643f10
--- /dev/null
+++ b/src/features/planSelection/api.js
@@ -0,0 +1,26 @@
1import { sendAuthRequest } from '../../api/utils/auth';
2import { API, API_VERSION } from '../../environment';
3import Request from '../../stores/lib/Request';
4
5const debug = require('debug')('Franz:feature:planSelection:api');
6
7export const planSelectionApi = {
8 downgrade: async () => {
9 const url = `${API}/${API_VERSION}/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
22export const downgradeUserRequest = new Request(planSelectionApi, 'downgrade');
23
24export const resetApiRequests = () => {
25 downgradeUserRequest.reset();
26};
diff --git a/src/features/planSelection/components/PlanItem.js b/src/features/planSelection/components/PlanItem.js
new file mode 100644
index 000000000..a49cd40d3
--- /dev/null
+++ b/src/features/planSelection/components/PlanItem.js
@@ -0,0 +1,186 @@
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 classnames from 'classnames';
7import color from 'color';
8
9import { H2 } from '@meetfranz/ui';
10
11import { Button } from '@meetfranz/forms';
12import { mdiArrowRight } from '@mdi/js';
13// import { FeatureList } from '../ui/FeatureList';
14// import { PLANS, PAYMENT_INTERVAL } from '../../config';
15// import { i18nPlanName, i18nIntervalName } from '../../helpers/plan-helpers';
16// import { PLAN_INTERVAL_CONFIG_TYPE } from './types';
17
18const messages = defineMessages({
19 perMonth: {
20 id: 'subscription.interval.perMonth',
21 defaultMessage: '!!!per month',
22 },
23 perMonthPerUser: {
24 id: 'subscription.interval.perMonthPerUser',
25 defaultMessage: '!!!per month & user',
26 },
27});
28
29const 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: 20,
42 fontSize: 30,
43 color: theme.styleTypes.primary.contrast,
44 // fontWeight: 'bold',
45 },
46 },
47 currency: {
48 fontSize: 35,
49 },
50 priceWrapper: {
51 height: 50,
52 },
53 price: {
54 fontSize: 50,
55
56 '& sup': {
57 fontSize: 20,
58 verticalAlign: 20,
59 },
60 },
61 interval: {
62 // paddingBottom: 40,
63 },
64 text: {
65 marginBottom: 'auto',
66 },
67 cta: {
68 background: theme.styleTypes.primary.accent,
69 color: theme.styleTypes.primary.contrast,
70 margin: [40, 'auto', 0, 'auto'],
71
72 // '&:active': {
73 // opacity: 0.7,
74 // },
75 },
76 divider: {
77 width: 40,
78 border: 0,
79 borderTop: [1, 'solid', theme.styleTypes.primary.contrast],
80 margin: [30, 'auto'],
81 },
82 header: {
83 padding: 20,
84 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
85 color: theme.styleTypes.primary.contrast,
86 },
87 content: {
88 padding: 20,
89 // border: [1, 'solid', 'red'],
90 background: '#EFEFEF',
91 },
92 simpleCTA: {
93 background: 'none',
94 color: theme.styleTypes.primary.accent,
95
96 '& svg': {
97 fill: theme.styleTypes.primary.accent,
98 },
99 },
100});
101
102
103export default @observer @injectSheet(styles) class PlanItem extends Component {
104 static propTypes = {
105 name: PropTypes.string.isRequired,
106 text: PropTypes.string.isRequired,
107 price: PropTypes.number.isRequired,
108 currency: PropTypes.string.isRequired,
109 upgrade: PropTypes.func.isRequired,
110 ctaLabel: PropTypes.string.isRequired,
111 simpleCTA: PropTypes.bool,
112 perUser: PropTypes.bool,
113 classes: PropTypes.object.isRequired,
114 children: PropTypes.element,
115 };
116
117 static defaultProps = {
118 simpleCTA: false,
119 perUser: false,
120 children: null,
121 }
122
123 static contextTypes = {
124 intl: intlShape,
125 };
126
127 render() {
128 const {
129 name,
130 text,
131 price,
132 currency,
133 classes,
134 upgrade,
135 ctaLabel,
136 simpleCTA,
137 perUser,
138 children,
139 } = this.props;
140 const { intl } = this.context;
141
142 const priceParts = `${price}`.split('.');
143 // const intervalName = i18nIntervalName(PAYMENT_INTERVAL.MONTHLY, intl);
144
145 return (
146 <div className={classes.root}>
147 <div className={classes.header}>
148 <H2 className={classes.planName}>{name}</H2>
149 <p className={classes.text}>
150 {text}
151 </p>
152 <hr className={classes.divider} />
153 <p className={classes.priceWrapper}>
154 <span className={classes.currency}>{currency}</span>
155 <span className={classes.price}>
156 {priceParts[0]}
157 <sup>{priceParts[1]}</sup>
158 </span>
159 </p>
160 <p className={classes.interval}>
161 {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)}
162 </p>
163 </div>
164
165 <div className={classes.content}>
166 {children}
167
168 <Button
169 className={classnames({
170 [classes.cta]: true,
171 [classes.simpleCTA]: simpleCTA,
172 })}
173 icon={simpleCTA ? mdiArrowRight : null}
174 label={(
175 <>
176 {ctaLabel}
177 </>
178 )}
179 onClick={upgrade}
180 />
181 </div>
182
183 </div>
184 );
185 }
186}
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
new file mode 100644
index 000000000..84d2d9e89
--- /dev/null
+++ b/src/features/planSelection/components/PlanSelection.js
@@ -0,0 +1,233 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import injectSheet from 'react-jss';
5import { defineMessages, intlShape } from 'react-intl';
6import { H1, H2, Icon } from '@meetfranz/ui';
7import color from 'color';
8
9import { mdiRocket } from '@mdi/js';
10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { PLANS } from '../../../config';
13import { FeatureList } from '../../../components/ui/FeatureList';
14
15const messages = defineMessages({
16 welcome: {
17 id: 'feature.planSelection.fullscreen.welcome',
18 defaultMessage: '!!!Welcome back, {name}',
19 },
20 subheadline: {
21 id: 'feature.planSelection.fullscreen.subheadline',
22 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.',
23 },
24 textFree: {
25 id: 'feature.planSelection.free.text',
26 defaultMessage: '!!!Basic functionality',
27 },
28 textPersonal: {
29 id: 'feature.planSelection.personal.text',
30 defaultMessage: '!!!More services, no waiting - ideal for personal use.',
31 },
32 textProfessional: {
33 id: 'feature.planSelection.pro.text',
34 defaultMessage: '!!!Unlimited services and professional features for you - and your team.',
35 },
36 ctaStayOnFree: {
37 id: 'feature.planSelection.cta.stayOnFree',
38 defaultMessage: '!!!Stay on Free',
39 },
40 ctaDowngradeFree: {
41 id: 'feature.planSelection.cta.ctaDowngradeFree',
42 defaultMessage: '!!!Downgrade to Free',
43 },
44 actionTrial: {
45 id: 'feature.planSelection.cta.trial',
46 defaultMessage: '!!!Start my free 14-days Trial',
47 },
48 shortActionPersonal: {
49 id: 'feature.planSelection.cta.upgradePersonal',
50 defaultMessage: '!!!Choose Personal',
51 },
52 shortActionPro: {
53 id: 'feature.planSelection.cta.upgradePro',
54 defaultMessage: '!!!Choose Professional',
55 },
56 fullFeatureList: {
57 id: 'feature.planSelection.fullFeatureList',
58 defaultMessage: '!!!Complete comparison of all plans',
59 },
60});
61
62const styles = theme => ({
63 root: {
64 background: theme.colorModalOverlayBackground,
65 width: '100%',
66 height: '100%',
67 position: 'absolute',
68 top: 0,
69 left: 0,
70 display: 'flex',
71 justifyContent: 'center',
72 alignItems: 'center',
73 zIndex: 999999,
74 },
75 container: {
76 width: '80%',
77 height: 'auto',
78 background: theme.styleTypes.primary.accent,
79 padding: 40,
80 borderRadius: theme.borderRadius,
81 maxWidth: 1000,
82
83 '& h1, & h2': {
84 textAlign: 'center',
85 },
86 },
87 plans: {
88 display: 'flex',
89 margin: [40, 0, 0],
90 height: 'auto',
91
92 '& > div': {
93 margin: [0, 15],
94 flex: 1,
95 height: 'auto',
96 background: theme.styleTypes.primary.contrast,
97 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
98 },
99 },
100 bigIcon: {
101 background: theme.styleTypes.danger.accent,
102 width: 120,
103 height: 120,
104 display: 'flex',
105 alignItems: 'center',
106 borderRadius: '100%',
107 justifyContent: 'center',
108 margin: [-100, 'auto', 20],
109
110 '& svg': {
111 width: '80px !important',
112 },
113 },
114 headline: {
115 fontSize: 40,
116 },
117 subheadline: {
118 maxWidth: 660,
119 fontSize: 22,
120 lineHeight: 1.1,
121 margin: [0, 'auto'],
122 },
123 featureList: {
124 '& li': {
125 borderBottom: [1, 'solid', '#CECECE'],
126 },
127 },
128 fullFeatureList: {
129 marginTop: 40,
130 textAlign: 'center',
131 display: 'block',
132 color: `${theme.styleTypes.primary.contrast} !important`,
133 },
134});
135
136@injectSheet(styles) @observer
137class PlanSelection extends Component {
138 static propTypes = {
139 classes: PropTypes.object.isRequired,
140 firstname: PropTypes.string.isRequired,
141 plans: PropTypes.object.isRequired,
142 currency: PropTypes.string.isRequired,
143 subscriptionExpired: PropTypes.bool.isRequired,
144 upgradeAccount: PropTypes.func.isRequired,
145 stayOnFree: PropTypes.func.isRequired,
146 hadSubscription: PropTypes.bool.isRequired,
147 };
148
149 static contextTypes = {
150 intl: intlShape,
151 };
152
153 render() {
154 const {
155 classes,
156 firstname,
157 plans,
158 currency,
159 subscriptionExpired,
160 upgradeAccount,
161 stayOnFree,
162 hadSubscription,
163 } = this.props;
164
165 const { intl } = this.context;
166
167 return (
168 <div
169 className={classes.root}
170 >
171 <div className={classes.container}>
172 <div className={classes.bigIcon}>
173 <Icon icon={mdiRocket} />
174 </div>
175 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
176 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
177 <div className={classes.plans}>
178 <PlanItem
179 name={i18nPlanName(PLANS.FREE, intl)}
180 text={intl.formatMessage(messages.textFree)}
181 price={0}
182 currency={currency}
183 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
184 upgrade={() => stayOnFree()}
185 simpleCTA
186 >
187 <FeatureList
188 plan={PLANS.FREE}
189 className={classes.featureList}
190 />
191 </PlanItem>
192 <PlanItem
193 name={i18nPlanName(plans.personal.yearly.id, intl)}
194 text={intl.formatMessage(messages.textPersonal)}
195 price={plans.personal.yearly.price}
196 currency={currency}
197 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
198 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
199 >
200 <FeatureList
201 plan={PLANS.PERSONAL}
202 className={classes.featureList}
203 />
204 </PlanItem>
205 <PlanItem
206 name={i18nPlanName(plans.pro.yearly.id, intl)}
207 text={intl.formatMessage(messages.textProfessional)}
208 price={plans.pro.yearly.price}
209 currency={currency}
210 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
211 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
212 perUser
213 >
214 <FeatureList
215 plan={PLANS.PRO}
216 className={classes.featureList}
217 />
218 </PlanItem>
219 </div>
220 <a
221 href="https://meetfranz.com/pricing"
222 target="_blank"
223 className={classes.fullFeatureList}
224 >
225 {intl.formatMessage(messages.fullFeatureList)}
226 </a>
227 </div>
228 </div>
229 );
230 }
231}
232
233export default PlanSelection;
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js
new file mode 100644
index 000000000..b0d9b5ab5
--- /dev/null
+++ b/src/features/planSelection/containers/PlanSelectionScreen.js
@@ -0,0 +1,138 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4import { remote } from 'electron';
5import { defineMessages, intlShape } from 'react-intl';
6
7import FeaturesStore from '../../../stores/FeaturesStore';
8import UserStore from '../../../stores/UserStore';
9import PlanSelection from '../components/PlanSelection';
10import ErrorBoundary from '../../../components/util/ErrorBoundary';
11import { planSelectionStore } from '..';
12
13const { dialog, app } = remote;
14
15const messages = defineMessages({
16 dialogTitle: {
17 id: 'feature.planSelection.fullscreen.dialog.title',
18 defaultMessage: '!!!Downgrade your Franz Plan',
19 },
20 dialogMessage: {
21 id: 'feature.planSelection.fullscreen.dialog.message',
22 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.',
23 },
24 dialogCTADowngrade: {
25 id: 'feature.planSelection.fullscreen.dialog.cta.downgrade',
26 defaultMessage: '!!!Downgrade to Free',
27 },
28 dialogCTAUpgrade: {
29 id: 'feature.planSelection.fullscreen.dialog.cta.upgrade',
30 defaultMessage: '!!!Choose Personal',
31 },
32});
33
34@inject('stores', 'actions') @observer
35class PlanSelectionScreen extends Component {
36 static contextTypes = {
37 intl: intlShape,
38 };
39
40 upgradeAccount(planId) {
41 const { user, features } = this.props.stores;
42 const { upgradeAccount, hideOverlay } = this.props.actions.planSelection;
43
44 upgradeAccount({
45 planId,
46 onCloseWindow: () => {
47 hideOverlay();
48 user.getUserInfoRequest.invalidate({ immediately: true });
49 features.featuresRequest.invalidate({ immediately: true });
50 },
51 });
52 }
53
54 render() {
55 if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) {
56 return null;
57 }
58
59 const { intl } = this.context;
60
61 const { user, features } = this.props.stores;
62 const { plans, currency } = features.features.pricingConfig;
63 const { activateTrial } = this.props.actions.user;
64 const { upgradeAccount, downgradeAccount, hideOverlay } = this.props.actions.planSelection;
65
66 // const planConfig = [{
67 // id: 'free',
68 // price: 0,
69 // }, {
70 // id: plans.personal.yearly.id,
71 // price: plans.personal.yearly.price,
72 // }, {
73 // id: plans.pro.yearly.id,
74 // price: plans.pro.yearly.price,
75 // }];
76
77 return (
78 <ErrorBoundary>
79 <PlanSelection
80 firstname={user.data.firstname}
81 plans={plans}
82 currency={currency}
83 upgradeAccount={(planId) => {
84 if (user.data.hadSubscription) {
85 this.upgradeAccount(planId);
86 } else {
87 activateTrial({
88 planId,
89 });
90 }
91 }}
92 stayOnFree={() => {
93 const selection = dialog.showMessageBoxSync(app.mainWindow, {
94 type: 'question',
95 message: intl.formatMessage(messages.dialogTitle),
96 detail: intl.formatMessage(messages.dialogMessage, {
97 currency,
98 price: plans.personal.yearly.price,
99 }),
100 buttons: [
101 intl.formatMessage(messages.dialogCTADowngrade),
102 intl.formatMessage(messages.dialogCTAUpgrade),
103 ],
104 });
105
106 if (selection === 0) {
107 downgradeAccount();
108 hideOverlay();
109 } else {
110 upgradeAccount(plans.personal.yearly.id);
111 }
112 }}
113 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded}
114 hadSubscription={user.data.hadSubscription}
115 />
116 </ErrorBoundary>
117 );
118 }
119}
120
121export default PlanSelectionScreen;
122
123PlanSelectionScreen.wrappedComponent.propTypes = {
124 stores: PropTypes.shape({
125 features: PropTypes.instanceOf(FeaturesStore).isRequired,
126 user: PropTypes.instanceOf(UserStore).isRequired,
127 }).isRequired,
128 actions: PropTypes.shape({
129 planSelection: PropTypes.shape({
130 upgradeAccount: PropTypes.func.isRequired,
131 downgradeAccount: PropTypes.func.isRequired,
132 hideOverlay: PropTypes.func.isRequired,
133 }),
134 user: PropTypes.shape({
135 activateTrial: PropTypes.func.isRequired,
136 }),
137 }).isRequired,
138};
diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js
new file mode 100644
index 000000000..81189207a
--- /dev/null
+++ b/src/features/planSelection/index.js
@@ -0,0 +1,30 @@
1import { reaction } from 'mobx';
2import PlanSelectionStore from './store';
3
4const debug = require('debug')('Franz:feature:planSelection');
5
6export const GA_CATEGORY_PLAN_SELECTION = 'planSelection';
7
8export const planSelectionStore = new PlanSelectionStore();
9
10export default function initPlanSelection(stores, actions) {
11 stores.planSelection = planSelectionStore;
12 const { features } = stores;
13
14 // Toggle planSelection feature
15 reaction(
16 () => features.features.isPlanSelectionEnabled,
17 (isEnabled) => {
18 if (isEnabled) {
19 debug('Initializing `planSelection` feature');
20 planSelectionStore.start(stores, actions);
21 } else if (planSelectionStore.isFeatureActive) {
22 debug('Disabling `planSelection` feature');
23 planSelectionStore.stop();
24 }
25 },
26 {
27 fireImmediately: true,
28 },
29 );
30}
diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js
new file mode 100644
index 000000000..50e46dfb3
--- /dev/null
+++ b/src/features/planSelection/store.js
@@ -0,0 +1,113 @@
1import {
2 action,
3 observable,
4 computed,
5} from 'mobx';
6import { remote } from 'electron';
7
8import { planSelectionActions } from './actions';
9import { FeatureStore } from '../utils/FeatureStore';
10// import { createReactions } from '../../stores/lib/Reaction';
11import { createActionBindings } from '../utils/ActionBinding';
12import { downgradeUserRequest } from './api';
13
14const debug = require('debug')('Franz:feature:planSelection:store');
15
16const { BrowserWindow } = remote;
17
18export default class PlanSelectionStore extends FeatureStore {
19 @observable isFeatureEnabled = false;
20
21 @observable isFeatureActive = false;
22
23 @observable hideOverlay = false;
24
25 @computed get showPlanSelectionOverlay() {
26 const { team } = this.stores.user;
27 if (team && !this.hideOverlay) {
28 return team.state === 'expired' && !team.userHasDowngraded;
29 }
30
31 return false;
32 }
33
34 // ========== PUBLIC API ========= //
35
36 @action start(stores, actions, api) {
37 debug('PlanSelectionStore::start');
38 this.stores = stores;
39 this.actions = actions;
40 this.api = api;
41
42 // ACTIONS
43
44 this._registerActions(createActionBindings([
45 [planSelectionActions.upgradeAccount, this._upgradeAccount],
46 [planSelectionActions.downgradeAccount, this._downgradeAccount],
47 [planSelectionActions.hideOverlay, this._hideOverlay],
48 ]));
49
50 // REACTIONS
51
52 // this._allReactions = createReactions([
53 // this._setFeatureEnabledReaction,
54 // this._updateTodosConfig,
55 // this._firstLaunchReaction,
56 // this._routeCheckReaction,
57 // ]);
58
59 // this._registerReactions(this._allReactions);
60
61 this.isFeatureActive = true;
62 }
63
64 @action stop() {
65 super.stop();
66 debug('PlanSelectionStore::stop');
67 this.reset();
68 this.isFeatureActive = false;
69 }
70
71 // ========== PRIVATE METHODS ========= //
72
73 // Actions
74
75 @action _upgradeAccount = ({ planId, onCloseWindow = () => null }) => {
76 let hostedPageURL = this.stores.features.features.subscribeURL;
77
78 const parsedUrl = new URL(hostedPageURL);
79 const params = new URLSearchParams(parsedUrl.search.slice(1));
80
81 params.set('plan', planId);
82
83 hostedPageURL = this.stores.user.getAuthURL(`${parsedUrl.origin}${parsedUrl.pathname}?${params.toString()}`);
84
85 const win = new BrowserWindow({
86 parent: remote.getCurrentWindow(),
87 modal: true,
88 title: '🔒 Upgrade Your Franz Account',
89 width: 800,
90 height: window.innerHeight - 100,
91 maxWidth: 800,
92 minWidth: 600,
93 webPreferences: {
94 nodeIntegration: true,
95 webviewTag: true,
96 },
97 });
98 win.loadURL(`file://${__dirname}/../../index.html#/payment/${encodeURIComponent(hostedPageURL)}`);
99
100 win.on('closed', () => {
101 onCloseWindow();
102 });
103 };
104
105 @action _downgradeAccount = () => {
106 console.log('downgrade to free', downgradeUserRequest);
107 downgradeUserRequest.execute();
108 }
109
110 @action _hideOverlay = () => {
111 this.hideOverlay = true;
112 }
113}
diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js
index 87deacef4..a39d7a6e6 100644
--- a/src/features/shareFranz/index.js
+++ b/src/features/shareFranz/index.js
@@ -3,6 +3,7 @@ import ms from 'ms';
3 3
4import { state as delayAppState } from '../delayApp'; 4import { state as delayAppState } from '../delayApp';
5import { gaEvent, gaPage } from '../../lib/analytics'; 5import { gaEvent, gaPage } from '../../lib/analytics';
6import { planSelectionStore } from '../planSelection';
6 7
7export { default as Component } from './Component'; 8export { default as Component } from './Component';
8 9
@@ -35,7 +36,7 @@ export default function initialize(stores) {
35 () => stores.user.isLoggedIn, 36 () => stores.user.isLoggedIn,
36 () => { 37 () => {
37 setTimeout(() => { 38 setTimeout(() => {
38 if (stores.settings.stats.appStarts % 50 === 0) { 39 if (stores.settings.stats.appStarts % 50 === 0 && !planSelectionStore.showPlanSelectionOverlay) {
39 if (delayAppState.isDelayAppScreenVisible) { 40 if (delayAppState.isDelayAppScreenVisible) {
40 debug('Delaying share modal by 5 minutes'); 41 debug('Delaying share modal by 5 minutes');
41 setTimeout(() => showModal(), ms('5m')); 42 setTimeout(() => showModal(), ms('5m'));