aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2019-02-25 16:34:18 +0100
committerLibravatar Stefan Malzner <stefan@adlk.io>2019-02-25 16:34:18 +0100
commit2d4ee04e874e4420aa940c148c77c977188b9500 (patch)
treea2435b5718f67a6588e5bbb5fd8e82c10fb5c9b4 /src
parentremove code (diff)
downloadferdium-app-2d4ee04e874e4420aa940c148c77c977188b9500.tar.gz
ferdium-app-2d4ee04e874e4420aa940c148c77c977188b9500.tar.zst
ferdium-app-2d4ee04e874e4420aa940c148c77c977188b9500.zip
Add dialog to share franz on social media
Diffstat (limited to 'src')
-rw-r--r--src/components/layout/AppLayout.js2
-rw-r--r--src/components/ui/Modal/index.js26
-rw-r--r--src/components/ui/Modal/styles.js1
-rw-r--r--src/features/basicAuth/Component.js1
-rw-r--r--src/features/basicAuth/index.js3
-rw-r--r--src/features/delayApp/index.js6
-rw-r--r--src/features/shareFranz/Component.js160
-rw-r--r--src/features/shareFranz/index.js52
-rw-r--r--src/i18n/locales/en-US.json9
-rw-r--r--src/stores/FeaturesStore.js2
10 files changed, 251 insertions, 11 deletions
diff --git a/src/components/layout/AppLayout.js b/src/components/layout/AppLayout.js
index bce792e56..593149e72 100644
--- a/src/components/layout/AppLayout.js
+++ b/src/components/layout/AppLayout.js
@@ -7,6 +7,7 @@ import { TitleBar } from 'electron-react-titlebar';
7import InfoBar from '../ui/InfoBar'; 7import InfoBar from '../ui/InfoBar';
8import { Component as DelayApp } from '../../features/delayApp'; 8import { Component as DelayApp } from '../../features/delayApp';
9import { Component as BasicAuth } from '../../features/basicAuth'; 9import { Component as BasicAuth } from '../../features/basicAuth';
10import { Component as ShareFranz } from '../../features/shareFranz';
10import ErrorBoundary from '../util/ErrorBoundary'; 11import ErrorBoundary from '../util/ErrorBoundary';
11 12
12// import globalMessages from '../../i18n/globalMessages'; 13// import globalMessages from '../../i18n/globalMessages';
@@ -164,6 +165,7 @@ export default @observer class AppLayout extends Component {
164 )} 165 )}
165 {isDelayAppScreenVisible && (<DelayApp />)} 166 {isDelayAppScreenVisible && (<DelayApp />)}
166 <BasicAuth /> 167 <BasicAuth />
168 <ShareFranz />
167 {services} 169 {services}
168 </div> 170 </div>
169 </div> 171 </div>
diff --git a/src/components/ui/Modal/index.js b/src/components/ui/Modal/index.js
index d84e4c713..8e6ec5a0e 100644
--- a/src/components/ui/Modal/index.js
+++ b/src/components/ui/Modal/index.js
@@ -5,6 +5,9 @@ import classnames from 'classnames';
5import injectCSS from 'react-jss'; 5import injectCSS from 'react-jss';
6 6
7import styles from './styles'; 7import styles from './styles';
8import { Icon } from '../../../../packages/ui/lib';
9
10// ReactModal.setAppElement('#root');
8 11
9export default @injectCSS(styles) class Modal extends Component { 12export default @injectCSS(styles) class Modal extends Component {
10 static propTypes = { 13 static propTypes = {
@@ -14,11 +17,15 @@ export default @injectCSS(styles) class Modal extends Component {
14 isOpen: PropTypes.bool.isRequired, 17 isOpen: PropTypes.bool.isRequired,
15 portal: PropTypes.string, 18 portal: PropTypes.string,
16 close: PropTypes.func.isRequired, 19 close: PropTypes.func.isRequired,
20 shouldCloseOnOverlayClick: PropTypes.bool,
21 showClose: PropTypes.bool,
17 } 22 }
18 23
19 static defaultProps = { 24 static defaultProps = {
20 className: null, 25 className: null,
21 portal: 'modal-portal', 26 portal: 'modal-portal',
27 shouldCloseOnOverlayClick: false,
28 showClose: true,
22 } 29 }
23 30
24 render() { 31 render() {
@@ -29,6 +36,8 @@ export default @injectCSS(styles) class Modal extends Component {
29 isOpen, 36 isOpen,
30 portal, 37 portal,
31 close, 38 close,
39 shouldCloseOnOverlayClick,
40 showClose,
32 } = this.props; 41 } = this.props;
33 42
34 return ( 43 return (
@@ -42,14 +51,17 @@ export default @injectCSS(styles) class Modal extends Component {
42 overlayClassName={classes.overlay} 51 overlayClassName={classes.overlay}
43 portal={portal} 52 portal={portal}
44 onRequestClose={close} 53 onRequestClose={close}
54 shouldCloseOnOverlayClick={shouldCloseOnOverlayClick}
45 > 55 >
46 {/* <button 56 {showClose && close && (
47 type="button" 57 <button
48 className={classnames({ 58 type="button"
49 [`${classes.close}`]: true, 59 className={classes.close}
50 'mdi mdi-close': true, 60 onClick={close}
51 })} 61 >
52 /> */} 62 <Icon icon="mdiClose" size={1.5} />
63 </button>
64 )}
53 <div className={classes.content}> 65 <div className={classes.content}>
54 {children} 66 {children}
55 </div> 67 </div>
diff --git a/src/components/ui/Modal/styles.js b/src/components/ui/Modal/styles.js
index 56fecbf55..49b970c97 100644
--- a/src/components/ui/Modal/styles.js
+++ b/src/components/ui/Modal/styles.js
@@ -28,5 +28,6 @@ export default theme => ({
28 position: 'absolute', 28 position: 'absolute',
29 top: 0, 29 top: 0,
30 right: 0, 30 right: 0,
31 padding: 20,
31 }, 32 },
32}); 33});
diff --git a/src/features/basicAuth/Component.js b/src/features/basicAuth/Component.js
index 13395fb40..a8252acb7 100644
--- a/src/features/basicAuth/Component.js
+++ b/src/features/basicAuth/Component.js
@@ -62,6 +62,7 @@ export default @injectSheet(styles) @observer class BasicAuthModal extends Compo
62 isOpen={isModalVisible} 62 isOpen={isModalVisible}
63 className={classes.modal} 63 className={classes.modal}
64 close={this.cancel.bind(this)} 64 close={this.cancel.bind(this)}
65 showClose={false}
65 > 66 >
66 <h1>Sign in</h1> 67 <h1>Sign in</h1>
67 <p> 68 <p>
diff --git a/src/features/basicAuth/index.js b/src/features/basicAuth/index.js
index 00ad65ce6..89607824b 100644
--- a/src/features/basicAuth/index.js
+++ b/src/features/basicAuth/index.js
@@ -6,7 +6,7 @@ import BasicAuthComponent from './Component';
6const debug = require('debug')('Franz:feature:basicAuth'); 6const debug = require('debug')('Franz:feature:basicAuth');
7 7
8const defaultState = { 8const defaultState = {
9 isModalVisible: false, 9 isModalVisible: true,
10 service: null, 10 service: null,
11 authInfo: null, 11 authInfo: null,
12}; 12};
@@ -15,7 +15,6 @@ export const state = observable(defaultState);
15 15
16export function resetState() { 16export function resetState() {
17 Object.assign(state, defaultState); 17 Object.assign(state, defaultState);
18 console.log('reset state', state);
19} 18}
20 19
21export default function initialize() { 20export default function initialize() {
diff --git a/src/features/delayApp/index.js b/src/features/delayApp/index.js
index 48aac34b6..abc8274cf 100644
--- a/src/features/delayApp/index.js
+++ b/src/features/delayApp/index.js
@@ -28,8 +28,12 @@ export default function init(stores) {
28 let shownAfterLaunch = false; 28 let shownAfterLaunch = false;
29 let timeLastDelay = moment(); 29 let timeLastDelay = moment();
30 30
31 window.franz.features.delayApp = {
32 state,
33 };
34
31 reaction( 35 reaction(
32 () => stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, 36 () => stores.user.isLoggedIn && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium,
33 (isEnabled) => { 37 (isEnabled) => {
34 if (isEnabled) { 38 if (isEnabled) {
35 debug('Enabling `delayApp` feature'); 39 debug('Enabling `delayApp` feature');
diff --git a/src/features/shareFranz/Component.js b/src/features/shareFranz/Component.js
new file mode 100644
index 000000000..753176e9c
--- /dev/null
+++ b/src/features/shareFranz/Component.js
@@ -0,0 +1,160 @@
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 { Button } from '@meetfranz/forms';
7import { H1, Icon } from '@meetfranz/ui';
8
9import Modal from '../../components/ui/Modal';
10import { state } from '.';
11import { gaEvent } from '../../lib/analytics';
12
13const messages = defineMessages({
14 headline: {
15 id: 'feature.shareFranz.headline',
16 defaultMessage: '!!!Franz is better together!',
17 },
18 text: {
19 id: 'feature.shareFranz.text',
20 defaultMessage: '!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.',
21 },
22 actions: {
23 email: {
24 id: 'feature.shareFranz.action.email',
25 defaultMessage: '!!!Share as email',
26 },
27 facebook: {
28 id: 'feature.shareFranz.action.facebook',
29 defaultMessage: '!!!Share on Facebook',
30 },
31 twitter: {
32 id: 'feature.shareFranz.action.twitter',
33 defaultMessage: '!!!Share on Twitter',
34 },
35 },
36 shareText: {
37 email: {
38 id: 'feature.shareFranz.shareText.email',
39 defaultMessage: '!!! I\'ve added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com',
40 },
41 twitter: {
42 id: 'feature.shareFranz.shareText.twitter',
43 defaultMessage: '!!! I\'ve added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @MeetFranz',
44 },
45 },
46});
47
48const styles = theme => ({
49 modal: {
50 width: '80%',
51 maxWidth: 600,
52 background: theme.styleTypes.primary.accent,
53 textAlign: 'center',
54 color: theme.styleTypes.primary.contrast,
55 },
56 heartContainer: {
57 display: 'flex',
58 justifyContent: 'center',
59 borderRadius: '100%',
60 background: theme.brandDanger,
61 padding: 20,
62 width: 100,
63 height: 100,
64 margin: [-70, 'auto', 30],
65 },
66 heart: {
67 fill: theme.styleTypes.primary.contrast,
68 },
69 headline: {
70 textAlign: 'center',
71 fontSize: 40,
72 marginBottom: 20,
73 },
74 actions: {
75 display: 'flex',
76 justifyContent: 'space-between',
77 marginTop: 30,
78 },
79 cta: {
80 background: theme.styleTypes.primary.contrast,
81 color: theme.styleTypes.primary.accent,
82
83 '& svg': {
84 fill: theme.styleTypes.primary.accent,
85 },
86 },
87});
88
89export default @injectSheet(styles) @observer class ShareFranzModal extends Component {
90 static propTypes = {
91 classes: PropTypes.object.isRequired,
92 }
93
94 static contextTypes = {
95 intl: intlShape,
96 };
97
98 close() {
99 state.isModalVisible = false;
100 }
101
102 render() {
103 const { isModalVisible } = state;
104
105 const {
106 classes,
107 } = this.props;
108
109 const { intl } = this.context;
110
111 return (
112 <Modal
113 isOpen={isModalVisible}
114 className={classes.modal}
115 shouldCloseOnOverlayClick
116 close={this.close.bind(this)}
117 >
118 <div className={classes.heartContainer}>
119 <Icon icon="mdiHeart" className={classes.heart} size={4} />
120 </div>
121 <H1 className={classes.headline}>
122 {intl.formatMessage(messages.headline)}
123 </H1>
124 <p>{intl.formatMessage(messages.text)}</p>
125 <div className={classes.actions}>
126 <Button
127 label={intl.formatMessage(messages.actions.email)}
128 className={classes.cta}
129 icon="mdiEmail"
130 href={`mailto:?subject=Meet the cool app Franz&body=${intl.formatMessage(messages.shareText.email, { count: 10 })}}`}
131 target="_blank"
132 onClick={() => {
133 gaEvent('Share Franz', 'share', 'Share via email');
134 }}
135 />
136 <Button
137 label={intl.formatMessage(messages.actions.facebook)}
138 className={classes.cta}
139 icon="mdiFacebookBox"
140 href="https://www.facebook.com/sharer/sharer.php?u=https://www.meetfranz.com?utm_source=facebook&utm_medium=referral&utm_campaign=share-button"
141 target="_blank"
142 onClick={() => {
143 gaEvent('Share Franz', 'share', 'Share via Facebook');
144 }}
145 />
146 <Button
147 label={intl.formatMessage(messages.actions.twitter)}
148 className={classes.cta}
149 icon="mdiTwitter"
150 href={`http://twitter.com/intent/tweet?status=${intl.formatMessage(messages.shareText.twitter, { count: 10 })}`}
151 target="_blank"
152 onClick={() => {
153 gaEvent('Share Franz', 'share', 'Share via Twitter');
154 }}
155 />
156 </div>
157 </Modal>
158 );
159 }
160}
diff --git a/src/features/shareFranz/index.js b/src/features/shareFranz/index.js
new file mode 100644
index 000000000..3a8ec95d3
--- /dev/null
+++ b/src/features/shareFranz/index.js
@@ -0,0 +1,52 @@
1import { observable, reaction } from 'mobx';
2import ms from 'ms';
3
4import { state as delayAppState } from '../delayApp';
5import { gaEvent, gaPage } from '../../lib/analytics';
6
7export { default as Component } from './Component';
8
9const debug = require('debug')('Franz:feature:shareFranz');
10
11const defaultState = {
12 isModalVisible: false,
13 lastShown: null,
14};
15
16export const state = observable(defaultState);
17
18export default function initialize(stores) {
19 debug('Initialize shareFranz feature');
20
21 window.franz.features.shareFranz = {
22 state,
23 };
24
25 function showModal() {
26 debug('Showing share window');
27
28 state.isModalVisible = true;
29
30 gaEvent('Share Franz', 'show');
31 gaPage('/share-modal');
32 }
33
34 reaction(
35 () => stores.user.isLoggedIn,
36 () => {
37 setTimeout(() => {
38 if (stores.settings.stats.appStarts % 30 === 0) {
39 if (delayAppState.isDelayAppScreenVisible) {
40 debug('Delaying share modal by 5 minutes');
41 setTimeout(() => showModal(), ms('5m'));
42 } else {
43 showModal();
44 }
45 }
46 }, ms('2s'));
47 },
48 {
49 fireImmediately: true,
50 },
51 );
52}
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json
index 4e0c5575d..25ec027d8 100644
--- a/src/i18n/locales/en-US.json
+++ b/src/i18n/locales/en-US.json
@@ -277,5 +277,12 @@
277 "feature.delayApp.text": "Franz will continue in {seconds} seconds.", 277 "feature.delayApp.text": "Franz will continue in {seconds} seconds.",
278 "premiumFeature.button.upgradeAccount": "Upgrade account", 278 "premiumFeature.button.upgradeAccount": "Upgrade account",
279 "app.errorHandler.headline": "Something went wrong", 279 "app.errorHandler.headline": "Something went wrong",
280 "app.errorHandler.action": "Reload" 280 "app.errorHandler.action": "Reload",
281 "feature.shareFranz.headline": "Franz is better together!",
282 "feature.shareFranz.text": "Tell your friends and colleagues how awesome Franz is and help us to spread the word.",
283 "feature.shareFranz.action.email": "Send as email",
284 "feature.shareFranz.action.facebook": "Share on Facebook",
285 "feature.shareFranz.action.twitter": "Share on Twitter",
286 "feature.shareFranz.shareText.email": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com",
287 "feature.shareFranz.shareText.twitter": "I've added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com /cc @MeetFranz"
281} 288}
diff --git a/src/stores/FeaturesStore.js b/src/stores/FeaturesStore.js
index 0adee6adf..d2842083c 100644
--- a/src/stores/FeaturesStore.js
+++ b/src/stores/FeaturesStore.js
@@ -7,6 +7,7 @@ import delayApp from '../features/delayApp';
7import spellchecker from '../features/spellchecker'; 7import spellchecker from '../features/spellchecker';
8import serviceProxy from '../features/serviceProxy'; 8import serviceProxy from '../features/serviceProxy';
9import basicAuth from '../features/basicAuth'; 9import basicAuth from '../features/basicAuth';
10import shareFranz from '../features/shareFranz';
10 11
11import { DEFAULT_FEATURES_CONFIG } from '../config'; 12import { DEFAULT_FEATURES_CONFIG } from '../config';
12 13
@@ -56,5 +57,6 @@ export default class FeaturesStore extends Store {
56 spellchecker(this.stores, this.actions); 57 spellchecker(this.stores, this.actions);
57 serviceProxy(this.stores, this.actions); 58 serviceProxy(this.stores, this.actions);
58 basicAuth(this.stores, this.actions); 59 basicAuth(this.stores, this.actions);
60 shareFranz(this.stores, this.actions);
59 } 61 }
60} 62}