aboutsummaryrefslogtreecommitdiffstats
path: root/src/features/planSelection
diff options
context:
space:
mode:
Diffstat (limited to 'src/features/planSelection')
-rw-r--r--src/features/planSelection/actions.js9
-rw-r--r--src/features/planSelection/api.js26
-rw-r--r--src/features/planSelection/components/PlanItem.js215
-rw-r--r--src/features/planSelection/components/PlanSelection.js269
-rw-r--r--src/features/planSelection/containers/PlanSelectionScreen.js120
-rw-r--r--src/features/planSelection/index.js28
-rw-r--r--src/features/planSelection/store.js68
7 files changed, 0 insertions, 735 deletions
diff --git a/src/features/planSelection/actions.js b/src/features/planSelection/actions.js
deleted file mode 100644
index 83f58bfd7..000000000
--- a/src/features/planSelection/actions.js
+++ /dev/null
@@ -1,9 +0,0 @@
1import PropTypes from 'prop-types';
2import { createActionsFromDefinitions } from '../../actions/lib/actions';
3
4export const planSelectionActions = createActionsFromDefinitions({
5 downgradeAccount: {},
6 hideOverlay: {},
7}, PropTypes.checkPropTypes);
8
9export default planSelectionActions;
diff --git a/src/features/planSelection/api.js b/src/features/planSelection/api.js
deleted file mode 100644
index 16bf9ff2d..000000000
--- a/src/features/planSelection/api.js
+++ /dev/null
@@ -1,26 +0,0 @@
1import { sendAuthRequest } from '../../api/utils/auth';
2import Request from '../../stores/lib/Request';
3import apiBase from '../../api/apiBase';
4
5const debug = require('debug')('Ferdi:feature:planSelection:api');
6
7export const planSelectionApi = {
8 downgrade: async () => {
9 const url = `${apiBase()}/payment/downgrade`;
10 const options = {
11 method: 'PUT',
12 };
13 debug('downgrade UPDATE', url, options);
14 const result = await sendAuthRequest(url, options);
15 debug('downgrade RESULT', result);
16 if (!result.ok) throw result;
17
18 return result.ok;
19 },
20};
21
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
deleted file mode 100644
index e90532dec..000000000
--- a/src/features/planSelection/components/PlanItem.js
+++ /dev/null
@@ -1,215 +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 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
14const messages = defineMessages({
15 perMonth: {
16 id: 'subscription.interval.perMonth',
17 defaultMessage: '!!!per month',
18 },
19 perMonthPerUser: {
20 id: 'subscription.interval.perMonthPerUser',
21 defaultMessage: '!!!per month & user',
22 },
23 bestValue: {
24 id: 'subscription.bestValue',
25 defaultMessage: '!!!Best value',
26 },
27});
28
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: 10,
42 fontSize: 30,
43 color: theme.styleTypes.primary.contrast,
44 },
45 },
46 currency: {
47 fontSize: 35,
48 },
49 priceWrapper: {
50 height: 50,
51 marginBottom: 0,
52 marginTop: ({ text }) => (!text ? 15 : 0),
53 },
54 price: {
55 fontSize: 50,
56
57 '& sup': {
58 fontSize: 20,
59 verticalAlign: 20,
60 },
61 },
62 text: {
63 marginBottom: 'auto',
64 },
65 cta: {
66 background: theme.styleTypes.primary.accent,
67 color: theme.styleTypes.primary.contrast,
68 margin: [30, 'auto', 0, 'auto'],
69 },
70 divider: {
71 width: 40,
72 border: 0,
73 borderTop: [1, 'solid', theme.styleTypes.primary.contrast],
74 margin: [15, 'auto', 20],
75 },
76 header: {
77 padding: 20,
78 background: color(theme.styleTypes.primary.accent).darken(0.25).hex(),
79 color: theme.styleTypes.primary.contrast,
80 position: 'relative',
81 height: 'auto',
82 },
83 content: {
84 padding: [10, 20, 20],
85 background: '#EFEFEF',
86 display: 'flex',
87 flexDirection: 'column',
88 justifyContent: 'space-between',
89 },
90 simpleCTA: {
91 background: 'none',
92 color: theme.styleTypes.primary.accent,
93
94 '& svg': {
95 fill: theme.styleTypes.primary.accent,
96 },
97 },
98 bestValue: {
99 background: theme.styleTypes.success.accent,
100 color: theme.styleTypes.success.contrast,
101 right: -66,
102 top: -40,
103 height: 'auto',
104 position: 'absolute',
105 transform: 'rotateZ(45deg)',
106 textAlign: 'center',
107 padding: [5, 50],
108 transformOrigin: 'left bottom',
109 fontSize: 12,
110 boxShadow: '0 2px 6px rgba(0,0,0,0.15)',
111 },
112});
113
114export default @observer @injectSheet(styles) class PlanItem extends Component {
115 static propTypes = {
116 name: PropTypes.string.isRequired,
117 text: PropTypes.string.isRequired,
118 price: PropTypes.number.isRequired,
119 currency: PropTypes.string.isRequired,
120 upgrade: PropTypes.func.isRequired,
121 ctaLabel: PropTypes.string.isRequired,
122 simpleCTA: PropTypes.bool,
123 perUser: PropTypes.bool,
124 classes: PropTypes.object.isRequired,
125 bestValue: PropTypes.bool,
126 className: PropTypes.string,
127 children: PropTypes.element,
128 };
129
130 static defaultProps = {
131 simpleCTA: false,
132 perUser: false,
133 children: null,
134 bestValue: false,
135 className: '',
136 }
137
138 static contextTypes = {
139 intl: intlShape,
140 };
141
142 render() {
143 const {
144 name,
145 text,
146 price,
147 currency,
148 classes,
149 upgrade,
150 ctaLabel,
151 simpleCTA,
152 perUser,
153 bestValue,
154 className,
155 children,
156 } = this.props;
157 const { intl } = this.context;
158
159 const priceParts = `${price}`.split('.');
160
161 return (
162 <div className={classnames({
163 [classes.root]: true,
164 [className]: className,
165 })}
166 >
167 <div className={classes.header}>
168 {bestValue && (
169 <div className={classes.bestValue}>
170 {intl.formatMessage(messages.bestValue)}
171 </div>
172 )}
173 <H2 className={classes.planName}>{name}</H2>
174 {text && (
175 <>
176 <p className={classes.text}>
177 {text}
178 </p>
179 <hr className={classes.divider} />
180 </>
181 )}
182 <p className={classes.priceWrapper}>
183 <span className={classes.currency}>{currency}</span>
184 <span className={classes.price}>
185 {priceParts[0]}
186 <sup>{priceParts[1]}</sup>
187 </span>
188 </p>
189 <p className={classes.interval}>
190 {intl.formatMessage(perUser ? messages.perMonthPerUser : messages.perMonth)}
191 </p>
192 </div>
193
194 <div className={classes.content}>
195 {children}
196
197 <Button
198 className={classnames({
199 [classes.cta]: true,
200 [classes.simpleCTA]: simpleCTA,
201 })}
202 icon={simpleCTA ? mdiArrowRight : null}
203 label={(
204 <>
205 {ctaLabel}
206 </>
207 )}
208 onClick={upgrade}
209 />
210 </div>
211
212 </div>
213 );
214 }
215}
diff --git a/src/features/planSelection/components/PlanSelection.js b/src/features/planSelection/components/PlanSelection.js
deleted file mode 100644
index 819a9df5b..000000000
--- a/src/features/planSelection/components/PlanSelection.js
+++ /dev/null
@@ -1,269 +0,0 @@
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 { mdiArrowRight } from '@mdi/js';
10import PlanItem from './PlanItem';
11import { i18nPlanName } from '../../../helpers/plan-helpers';
12import { DEV_API_FRANZ_WEBSITE, PLANS } from '../../../config';
13import { FeatureList } from '../../../components/ui/FeatureList';
14import Appear from '../../../components/ui/effects/Appear';
15
16const messages = defineMessages({
17 welcome: {
18 id: 'feature.planSelection.fullscreen.welcome',
19 defaultMessage: '!!!Are you ready to choose, {name}',
20 },
21 subheadline: {
22 id: 'feature.planSelection.fullscreen.subheadline',
23 defaultMessage: '!!!It\'s time to make a choice. Franz works best on our Personal and Professional plans. Please have a look and choose the best one for you.',
24 },
25 textFree: {
26 id: 'feature.planSelection.free.text',
27 defaultMessage: '!!!Basic functionality',
28 },
29 textPersonal: {
30 id: 'feature.planSelection.personal.text',
31 defaultMessage: '!!!More services, no waiting - ideal for personal use.',
32 },
33 textProfessional: {
34 id: 'feature.planSelection.pro.text',
35 defaultMessage: '!!!Unlimited services and professional features for you - and your team.',
36 },
37 ctaStayOnFree: {
38 id: 'feature.planSelection.cta.stayOnFree',
39 defaultMessage: '!!!Stay on Free',
40 },
41 ctaDowngradeFree: {
42 id: 'feature.planSelection.cta.ctaDowngradeFree',
43 defaultMessage: '!!!Downgrade to Free',
44 },
45 actionTrial: {
46 id: 'feature.planSelection.cta.trial',
47 defaultMessage: '!!!Start my free 14-days Trial',
48 },
49 shortActionPersonal: {
50 id: 'feature.planSelection.cta.upgradePersonal',
51 defaultMessage: '!!!Choose Personal',
52 },
53 shortActionPro: {
54 id: 'feature.planSelection.cta.upgradePro',
55 defaultMessage: '!!!Choose Professional',
56 },
57 fullFeatureList: {
58 id: 'feature.planSelection.fullFeatureList',
59 defaultMessage: '!!!Complete comparison of all plans',
60 },
61 pricesBasedOnAnnualPayment: {
62 id: 'feature.planSelection.pricesBasedOnAnnualPayment',
63 defaultMessage: '!!!All prices based on yearly payment',
64 },
65});
66
67const styles = theme => ({
68 root: {
69 background: theme.colorModalOverlayBackground,
70 width: '100%',
71 height: '100%',
72 position: 'absolute',
73 top: 0,
74 left: 0,
75 display: 'flex',
76 justifyContent: 'center',
77 alignItems: 'center',
78 zIndex: 999999,
79 overflowY: 'scroll',
80 },
81 container: {
82 // width: '80%',
83 height: 'auto',
84 // background: theme.styleTypes.primary.accent,
85 // padding: 40,
86 borderRadius: theme.borderRadius,
87 maxWidth: 1000,
88
89 '& h1, & h2': {
90 textAlign: 'center',
91 color: theme.styleTypes.primary.contrast,
92 },
93 },
94 plans: {
95 display: 'flex',
96 margin: [40, 0, 0],
97 height: 'auto',
98
99 '& > div': {
100 margin: [0, 15],
101 flex: 1,
102 height: 'auto',
103 background: theme.styleTypes.primary.contrast,
104 boxShadow: [0, 2, 30, color('#000').alpha(0.1).rgb().string()],
105 },
106 },
107 headline: {
108 fontSize: 40,
109 },
110 subheadline: {
111 maxWidth: 660,
112 fontSize: 22,
113 lineHeight: 1.1,
114 margin: [0, 'auto'],
115 },
116 featureList: {
117 '& li': {
118 borderBottom: [1, 'solid', '#CECECE'],
119 },
120 },
121 footer: {
122 display: 'flex',
123 color: theme.styleTypes.primary.contrast,
124 marginTop: 20,
125 padding: [0, 15],
126 },
127 fullFeatureList: {
128 marginRight: 'auto',
129 textAlign: 'center',
130 display: 'flex',
131 justifyContent: 'center',
132 alignItems: 'center',
133 color: `${theme.styleTypes.primary.contrast} !important`,
134
135 '& svg': {
136 marginRight: 5,
137 },
138 },
139 scrollContainer: {
140 border: '1px solid red',
141 overflow: 'scroll-x',
142 },
143 featuredPlan: {
144 transform: ({ isPersonalPlanAvailable }) => (isPersonalPlanAvailable ? 'scale(1.05)' : null),
145 },
146 disclaimer: {
147 textAlign: 'right',
148 margin: [10, 15, 0, 0],
149 },
150});
151
152@injectSheet(styles) @observer
153class PlanSelection extends Component {
154 static propTypes = {
155 classes: PropTypes.object.isRequired,
156 firstname: PropTypes.string.isRequired,
157 plans: PropTypes.object.isRequired,
158 currency: PropTypes.string.isRequired,
159 subscriptionExpired: PropTypes.bool.isRequired,
160 upgradeAccount: PropTypes.func.isRequired,
161 stayOnFree: PropTypes.func.isRequired,
162 hadSubscription: PropTypes.bool.isRequired,
163 isPersonalPlanAvailable: PropTypes.bool,
164 };
165
166 static defaultProps = {
167 isPersonalPlanAvailable: true,
168 }
169
170 static contextTypes = {
171 intl: intlShape,
172 };
173
174 componentDidMount() {
175 }
176
177 render() {
178 const {
179 classes,
180 firstname,
181 plans,
182 currency,
183 subscriptionExpired,
184 upgradeAccount,
185 stayOnFree,
186 hadSubscription,
187 isPersonalPlanAvailable,
188 } = this.props;
189
190 const { intl } = this.context;
191
192 return (
193 <Appear>
194 <div
195 className={classes.root}
196 >
197 <div className={classes.container}>
198 <H1 className={classes.headline}>{intl.formatMessage(messages.welcome, { name: firstname })}</H1>
199 {isPersonalPlanAvailable && (
200 <H2 className={classes.subheadline}>{intl.formatMessage(messages.subheadline)}</H2>
201 )}
202 <div className={classes.plans}>
203 <PlanItem
204 name={i18nPlanName(PLANS.FREE, intl)}
205 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textFree) : null}
206 price={0}
207 currency={currency}
208 ctaLabel={intl.formatMessage(subscriptionExpired ? messages.ctaDowngradeFree : messages.ctaStayOnFree)}
209 upgrade={() => stayOnFree()}
210 simpleCTA
211 >
212 <FeatureList
213 plan={PLANS.FREE}
214 className={classes.featureList}
215 />
216 </PlanItem>
217 <PlanItem
218 name={i18nPlanName(plans.pro.yearly.id, intl)}
219 text={isPersonalPlanAvailable ? intl.formatMessage(messages.textProfessional) : null}
220 price={plans.pro.yearly.price}
221 currency={currency}
222 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPro : messages.actionTrial)}
223 upgrade={() => upgradeAccount(plans.pro.yearly.id)}
224 className={classes.featuredPlan}
225 perUser
226 bestValue={isPersonalPlanAvailable}
227 >
228 <FeatureList
229 plan={isPersonalPlanAvailable ? PLANS.PRO : null}
230 className={classes.featureList}
231 />
232 </PlanItem>
233 {isPersonalPlanAvailable && (
234 <PlanItem
235 name={i18nPlanName(plans.personal.yearly.id, intl)}
236 text={intl.formatMessage(messages.textPersonal)}
237 price={plans.personal.yearly.price}
238 currency={currency}
239 ctaLabel={intl.formatMessage(hadSubscription ? messages.shortActionPersonal : messages.actionTrial)}
240 upgrade={() => upgradeAccount(plans.personal.yearly.id)}
241 >
242 <FeatureList
243 plan={PLANS.PERSONAL}
244 className={classes.featureList}
245 />
246 </PlanItem>
247 )}
248 </div>
249 <div className={classes.footer}>
250 <a
251 href={`${DEV_API_FRANZ_WEBSITE}/pricing`}
252 target="_blank"
253 className={classes.fullFeatureList}
254 >
255 <Icon icon={mdiArrowRight} />
256 {intl.formatMessage(messages.fullFeatureList)}
257 </a>
258 {/* <p className={classes.disclaimer}> */}
259 {intl.formatMessage(messages.pricesBasedOnAnnualPayment)}
260 {/* </p> */}
261 </div>
262 </div>
263 </div>
264 </Appear>
265 );
266 }
267}
268
269export default PlanSelection;
diff --git a/src/features/planSelection/containers/PlanSelectionScreen.js b/src/features/planSelection/containers/PlanSelectionScreen.js
deleted file mode 100644
index 594829c01..000000000
--- a/src/features/planSelection/containers/PlanSelectionScreen.js
+++ /dev/null
@@ -1,120 +0,0 @@
1import React, { Component } from 'react';
2import { observer, inject } from 'mobx-react';
3import PropTypes from 'prop-types';
4import { dialog, app } from '@electron/remote';
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 '..';
12import PaymentStore from '../../../stores/PaymentStore';
13
14const messages = defineMessages({
15 dialogTitle: {
16 id: 'feature.planSelection.fullscreen.dialog.title',
17 defaultMessage: '!!!Downgrade your Franz Plan',
18 },
19 dialogMessage: {
20 id: 'feature.planSelection.fullscreen.dialog.message',
21 defaultMessage: '!!!You\'re about to downgrade to our Free account. Are you sure? Click here instead to get more services and functionality for just {currency}{price} a month.',
22 },
23 dialogCTADowngrade: {
24 id: 'feature.planSelection.fullscreen.dialog.cta.downgrade',
25 defaultMessage: '!!!Downgrade to Free',
26 },
27 dialogCTAUpgrade: {
28 id: 'feature.planSelection.fullscreen.dialog.cta.upgrade',
29 defaultMessage: '!!!Choose Personal',
30 },
31});
32
33@inject('stores', 'actions') @observer
34class PlanSelectionScreen extends Component {
35 static contextTypes = {
36 intl: intlShape,
37 };
38
39 upgradeAccount(planId) {
40 const { upgradeAccount } = this.props.actions.payment;
41
42 upgradeAccount({
43 planId,
44 });
45 }
46
47 render() {
48 if (!planSelectionStore || !planSelectionStore.isFeatureActive || !planSelectionStore.showPlanSelectionOverlay) {
49 return null;
50 }
51
52 const { intl } = this.context;
53
54 const { user, features } = this.props.stores;
55 const { isPersonalPlanAvailable, pricingConfig } = features.features;
56 const { plans, currency } = pricingConfig;
57 const { activateTrial } = this.props.actions.user;
58 const { downgradeAccount, hideOverlay } = this.props.actions.planSelection;
59
60 return (
61 <ErrorBoundary>
62 <PlanSelection
63 firstname={user.data.firstname}
64 plans={plans}
65 currency={currency}
66 upgradeAccount={(planId) => {
67 if (user.data.hadSubscription) {
68 this.upgradeAccount(planId);
69 } else {
70 activateTrial({
71 planId,
72 });
73 }
74 }}
75 stayOnFree={() => {
76 const selection = dialog.showMessageBoxSync(app.mainWindow, {
77 type: 'question',
78 message: intl.formatMessage(messages.dialogTitle),
79 detail: intl.formatMessage(messages.dialogMessage, {
80 currency,
81 price: plans.personal.yearly.price,
82 }),
83 buttons: [
84 intl.formatMessage(messages.dialogCTADowngrade),
85 intl.formatMessage(messages.dialogCTAUpgrade),
86 ],
87 });
88
89 if (selection === 0) {
90 downgradeAccount();
91 hideOverlay();
92 } else {
93 this.upgradeAccount(plans.personal.yearly.id);
94 }
95 }}
96 subscriptionExpired={user.team && user.team.state === 'expired' && !user.team.userHasDowngraded}
97 hadSubscription={user.data.hadSubscription}
98 isPersonalPlanAvailable={isPersonalPlanAvailable}
99 />
100 </ErrorBoundary>
101 );
102 }
103}
104
105export default PlanSelectionScreen;
106
107PlanSelectionScreen.wrappedComponent.propTypes = {
108 stores: PropTypes.shape({
109 features: PropTypes.instanceOf(FeaturesStore).isRequired,
110 user: PropTypes.instanceOf(UserStore).isRequired,
111 }).isRequired,
112 actions: PropTypes.shape({
113 payment: PropTypes.instanceOf(PaymentStore),
114 planSelection: PropTypes.shape({
115 downgradeAccount: PropTypes.func.isRequired,
116 hideOverlay: PropTypes.func.isRequired,
117 }),
118 user: PropTypes.instanceOf(UserStore).isRequired,
119 }).isRequired,
120};
diff --git a/src/features/planSelection/index.js b/src/features/planSelection/index.js
deleted file mode 100644
index b96ad6d8f..000000000
--- a/src/features/planSelection/index.js
+++ /dev/null
@@ -1,28 +0,0 @@
1import { reaction } from 'mobx';
2import PlanSelectionStore from './store';
3
4const debug = require('debug')('Ferdi:feature:planSelection');
5
6export const planSelectionStore = new PlanSelectionStore();
7
8export default function initPlanSelection(stores, actions) {
9 stores.planSelection = planSelectionStore;
10 const { features } = stores;
11
12 // Toggle planSelection feature
13 reaction(
14 () => features.features.isPlanSelectionEnabled,
15 (isEnabled) => {
16 if (isEnabled) {
17 debug('Initializing `planSelection` feature');
18 planSelectionStore.start(stores, actions);
19 } else if (planSelectionStore.isFeatureActive) {
20 debug('Disabling `planSelection` feature');
21 planSelectionStore.stop();
22 }
23 },
24 {
25 fireImmediately: true,
26 },
27 );
28}
diff --git a/src/features/planSelection/store.js b/src/features/planSelection/store.js
deleted file mode 100644
index de8fc7584..000000000
--- a/src/features/planSelection/store.js
+++ /dev/null
@@ -1,68 +0,0 @@
1import {
2 action,
3 observable,
4 computed,
5} from 'mobx';
6
7import { planSelectionActions } from './actions';
8import { FeatureStore } from '../utils/FeatureStore';
9import { createActionBindings } from '../utils/ActionBinding';
10import { downgradeUserRequest } from './api';
11
12const debug = require('debug')('Ferdi:feature:planSelection:store');
13
14export default class PlanSelectionStore extends FeatureStore {
15 @observable isFeatureEnabled = false;
16
17 @observable isFeatureActive = false;
18
19 @observable hideOverlay = false;
20
21 @computed get showPlanSelectionOverlay() {
22 const { team, isPremium } = this.stores.user;
23 if (team && !this.hideOverlay && !isPremium) {
24 return team.state === 'expired' && !team.userHasDowngraded;
25 }
26
27 return false;
28 }
29
30 // ========== PUBLIC API ========= //
31
32 @action start(stores, actions, api) {
33 debug('PlanSelectionStore::start');
34 this.stores = stores;
35 this.actions = actions;
36 this.api = api;
37
38 // ACTIONS
39
40 this._registerActions(createActionBindings([
41 [planSelectionActions.downgradeAccount, this._downgradeAccount],
42 [planSelectionActions.hideOverlay, this._hideOverlay],
43 ]));
44
45 this.isFeatureActive = true;
46 }
47
48 @action stop() {
49 super.stop();
50 debug('PlanSelectionStore::stop');
51 this.isFeatureActive = false;
52 }
53
54 // ========== PRIVATE METHODS ========= //
55
56 // Actions
57 @action _downgradeAccount = () => {
58 downgradeUserRequest.execute();
59 }
60
61 @action _hideOverlay = () => {
62 this.hideOverlay = true;
63 }
64
65 @action _showOverlay = () => {
66 this.hideOverlay = false;
67 }
68}