diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/components/layout/AppLayout.js | 2 | ||||
-rw-r--r-- | src/components/ui/Modal/index.js | 26 | ||||
-rw-r--r-- | src/components/ui/Modal/styles.js | 1 | ||||
-rw-r--r-- | src/features/basicAuth/Component.js | 1 | ||||
-rw-r--r-- | src/features/basicAuth/index.js | 3 | ||||
-rw-r--r-- | src/features/delayApp/index.js | 6 | ||||
-rw-r--r-- | src/features/shareFranz/Component.js | 160 | ||||
-rw-r--r-- | src/features/shareFranz/index.js | 52 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 9 | ||||
-rw-r--r-- | src/stores/FeaturesStore.js | 2 |
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'; | |||
7 | import InfoBar from '../ui/InfoBar'; | 7 | import InfoBar from '../ui/InfoBar'; |
8 | import { Component as DelayApp } from '../../features/delayApp'; | 8 | import { Component as DelayApp } from '../../features/delayApp'; |
9 | import { Component as BasicAuth } from '../../features/basicAuth'; | 9 | import { Component as BasicAuth } from '../../features/basicAuth'; |
10 | import { Component as ShareFranz } from '../../features/shareFranz'; | ||
10 | import ErrorBoundary from '../util/ErrorBoundary'; | 11 | import 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'; | |||
5 | import injectCSS from 'react-jss'; | 5 | import injectCSS from 'react-jss'; |
6 | 6 | ||
7 | import styles from './styles'; | 7 | import styles from './styles'; |
8 | import { Icon } from '../../../../packages/ui/lib'; | ||
9 | |||
10 | // ReactModal.setAppElement('#root'); | ||
8 | 11 | ||
9 | export default @injectCSS(styles) class Modal extends Component { | 12 | export 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'; | |||
6 | const debug = require('debug')('Franz:feature:basicAuth'); | 6 | const debug = require('debug')('Franz:feature:basicAuth'); |
7 | 7 | ||
8 | const defaultState = { | 8 | const 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 | ||
16 | export function resetState() { | 16 | export function resetState() { |
17 | Object.assign(state, defaultState); | 17 | Object.assign(state, defaultState); |
18 | console.log('reset state', state); | ||
19 | } | 18 | } |
20 | 19 | ||
21 | export default function initialize() { | 20 | export 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 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import injectSheet from 'react-jss'; | ||
5 | import { defineMessages, intlShape } from 'react-intl'; | ||
6 | import { Button } from '@meetfranz/forms'; | ||
7 | import { H1, Icon } from '@meetfranz/ui'; | ||
8 | |||
9 | import Modal from '../../components/ui/Modal'; | ||
10 | import { state } from '.'; | ||
11 | import { gaEvent } from '../../lib/analytics'; | ||
12 | |||
13 | const 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 | |||
48 | const 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 | |||
89 | export 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 @@ | |||
1 | import { observable, reaction } from 'mobx'; | ||
2 | import ms from 'ms'; | ||
3 | |||
4 | import { state as delayAppState } from '../delayApp'; | ||
5 | import { gaEvent, gaPage } from '../../lib/analytics'; | ||
6 | |||
7 | export { default as Component } from './Component'; | ||
8 | |||
9 | const debug = require('debug')('Franz:feature:shareFranz'); | ||
10 | |||
11 | const defaultState = { | ||
12 | isModalVisible: false, | ||
13 | lastShown: null, | ||
14 | }; | ||
15 | |||
16 | export const state = observable(defaultState); | ||
17 | |||
18 | export 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'; | |||
7 | import spellchecker from '../features/spellchecker'; | 7 | import spellchecker from '../features/spellchecker'; |
8 | import serviceProxy from '../features/serviceProxy'; | 8 | import serviceProxy from '../features/serviceProxy'; |
9 | import basicAuth from '../features/basicAuth'; | 9 | import basicAuth from '../features/basicAuth'; |
10 | import shareFranz from '../features/shareFranz'; | ||
10 | 11 | ||
11 | import { DEFAULT_FEATURES_CONFIG } from '../config'; | 12 | import { 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 | } |