From 2d4ee04e874e4420aa940c148c77c977188b9500 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Mon, 25 Feb 2019 16:34:18 +0100 Subject: Add dialog to share franz on social media --- package-lock.json | 58 +++++++++-- package.json | 1 + packages/theme/src/themes/dark/index.ts | 2 +- packages/theme/src/themes/default/index.ts | 2 +- src/components/layout/AppLayout.js | 2 + src/components/ui/Modal/index.js | 26 +++-- src/components/ui/Modal/styles.js | 1 + src/features/basicAuth/Component.js | 1 + src/features/basicAuth/index.js | 3 +- src/features/delayApp/index.js | 6 +- src/features/shareFranz/Component.js | 160 +++++++++++++++++++++++++++++ src/features/shareFranz/index.js | 52 ++++++++++ src/i18n/locales/en-US.json | 9 +- src/stores/FeaturesStore.js | 2 + 14 files changed, 305 insertions(+), 20 deletions(-) create mode 100644 src/features/shareFranz/Component.js create mode 100644 src/features/shareFranz/index.js diff --git a/package-lock.json b/package-lock.json index bb768c834..395b45c13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4388,6 +4388,14 @@ "dev": true, "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "negotiator": { @@ -5172,6 +5180,13 @@ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } } }, "debug-electron": { @@ -7419,6 +7434,14 @@ "on-finished": "~2.3.0", "range-parser": "~1.2.0", "statuses": "~1.4.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "serve-static": { @@ -7886,6 +7909,14 @@ "dev": true, "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } } } @@ -8130,10 +8161,7 @@ }, "debug": { "version": "2.6.9", - "bundled": true, - "requires": { - "ms": "2.0.0" - } + "bundled": true }, "deep-extend": { "version": "0.6.0", @@ -10314,6 +10342,14 @@ "dev": true, "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } } } @@ -12457,6 +12493,14 @@ "dev": true, "requires": { "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } } }, "glob": { @@ -12521,9 +12565,9 @@ } }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" }, "multicast-dns": { "version": "6.2.3", diff --git a/package.json b/package.json index 912ee4662..f2fa57096 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "mobx-react-form": "1.35.1", "mobx-react-router": "3.1.2", "moment": "^2.17.1", + "ms": "2.1.1", "normalize-url": "^1.9.1", "pretty-bytes": "^4.0.2", "prop-types": "^15.5.10", diff --git a/packages/theme/src/themes/dark/index.ts b/packages/theme/src/themes/dark/index.ts index 1757f5abd..3a56719b2 100644 --- a/packages/theme/src/themes/dark/index.ts +++ b/packages/theme/src/themes/dark/index.ts @@ -62,4 +62,4 @@ export const selectOptionItemHoverColor = selectColor; export const selectSearchColor = inputBackground; // Modal -export const colorModalOverlayBackground = color(legacyStyles.darkThemeGray).alpha(0.8).rgb().string(); +export const colorModalOverlayBackground = color(legacyStyles.darkThemeBlack).alpha(0.8).rgb().string(); diff --git a/packages/theme/src/themes/default/index.ts b/packages/theme/src/themes/default/index.ts index 40521995b..8a71e61cf 100644 --- a/packages/theme/src/themes/default/index.ts +++ b/packages/theme/src/themes/default/index.ts @@ -139,4 +139,4 @@ export const badgeFontSize = uiFontSize - 2; export const badgeBorderRadius = 50; // Modal -export const colorModalOverlayBackground = color(legacyStyles.themeGrayLighter).alpha(0.8).rgb().string(); +export const colorModalOverlayBackground = color('#000').alpha(0.5).rgb().string(); 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'; import InfoBar from '../ui/InfoBar'; import { Component as DelayApp } from '../../features/delayApp'; import { Component as BasicAuth } from '../../features/basicAuth'; +import { Component as ShareFranz } from '../../features/shareFranz'; import ErrorBoundary from '../util/ErrorBoundary'; // import globalMessages from '../../i18n/globalMessages'; @@ -164,6 +165,7 @@ export default @observer class AppLayout extends Component { )} {isDelayAppScreenVisible && ()} + {services} 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'; import injectCSS from 'react-jss'; import styles from './styles'; +import { Icon } from '../../../../packages/ui/lib'; + +// ReactModal.setAppElement('#root'); export default @injectCSS(styles) class Modal extends Component { static propTypes = { @@ -14,11 +17,15 @@ export default @injectCSS(styles) class Modal extends Component { isOpen: PropTypes.bool.isRequired, portal: PropTypes.string, close: PropTypes.func.isRequired, + shouldCloseOnOverlayClick: PropTypes.bool, + showClose: PropTypes.bool, } static defaultProps = { className: null, portal: 'modal-portal', + shouldCloseOnOverlayClick: false, + showClose: true, } render() { @@ -29,6 +36,8 @@ export default @injectCSS(styles) class Modal extends Component { isOpen, portal, close, + shouldCloseOnOverlayClick, + showClose, } = this.props; return ( @@ -42,14 +51,17 @@ export default @injectCSS(styles) class Modal extends Component { overlayClassName={classes.overlay} portal={portal} onRequestClose={close} + shouldCloseOnOverlayClick={shouldCloseOnOverlayClick} > - {/* + )}
{children}
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 => ({ position: 'absolute', top: 0, right: 0, + padding: 20, }, }); 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 isOpen={isModalVisible} className={classes.modal} close={this.cancel.bind(this)} + showClose={false} >

Sign in

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'; const debug = require('debug')('Franz:feature:basicAuth'); const defaultState = { - isModalVisible: false, + isModalVisible: true, service: null, authInfo: null, }; @@ -15,7 +15,6 @@ export const state = observable(defaultState); export function resetState() { Object.assign(state, defaultState); - console.log('reset state', state); } 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) { let shownAfterLaunch = false; let timeLastDelay = moment(); + window.franz.features.delayApp = { + state, + }; + reaction( - () => stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, + () => stores.user.isLoggedIn && stores.features.features.needToWaitToProceed && !stores.user.data.isPremium, (isEnabled) => { if (isEnabled) { 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 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { observer } from 'mobx-react'; +import injectSheet from 'react-jss'; +import { defineMessages, intlShape } from 'react-intl'; +import { Button } from '@meetfranz/forms'; +import { H1, Icon } from '@meetfranz/ui'; + +import Modal from '../../components/ui/Modal'; +import { state } from '.'; +import { gaEvent } from '../../lib/analytics'; + +const messages = defineMessages({ + headline: { + id: 'feature.shareFranz.headline', + defaultMessage: '!!!Franz is better together!', + }, + text: { + id: 'feature.shareFranz.text', + defaultMessage: '!!!Tell your friends and colleagues how awesome Franz is and help us to spread the word.', + }, + actions: { + email: { + id: 'feature.shareFranz.action.email', + defaultMessage: '!!!Share as email', + }, + facebook: { + id: 'feature.shareFranz.action.facebook', + defaultMessage: '!!!Share on Facebook', + }, + twitter: { + id: 'feature.shareFranz.action.twitter', + defaultMessage: '!!!Share on Twitter', + }, + }, + shareText: { + email: { + id: 'feature.shareFranz.shareText.email', + defaultMessage: '!!! I\'ve added {count} services to Franz! Get the free app for WhatsApp, Messenger, Slack, Skype and co at www.meetfranz.com', + }, + twitter: { + id: 'feature.shareFranz.shareText.twitter', + 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', + }, + }, +}); + +const styles = theme => ({ + modal: { + width: '80%', + maxWidth: 600, + background: theme.styleTypes.primary.accent, + textAlign: 'center', + color: theme.styleTypes.primary.contrast, + }, + heartContainer: { + display: 'flex', + justifyContent: 'center', + borderRadius: '100%', + background: theme.brandDanger, + padding: 20, + width: 100, + height: 100, + margin: [-70, 'auto', 30], + }, + heart: { + fill: theme.styleTypes.primary.contrast, + }, + headline: { + textAlign: 'center', + fontSize: 40, + marginBottom: 20, + }, + actions: { + display: 'flex', + justifyContent: 'space-between', + marginTop: 30, + }, + cta: { + background: theme.styleTypes.primary.contrast, + color: theme.styleTypes.primary.accent, + + '& svg': { + fill: theme.styleTypes.primary.accent, + }, + }, +}); + +export default @injectSheet(styles) @observer class ShareFranzModal extends Component { + static propTypes = { + classes: PropTypes.object.isRequired, + } + + static contextTypes = { + intl: intlShape, + }; + + close() { + state.isModalVisible = false; + } + + render() { + const { isModalVisible } = state; + + const { + classes, + } = this.props; + + const { intl } = this.context; + + return ( + +

+ +
+

+ {intl.formatMessage(messages.headline)} +

+

{intl.formatMessage(messages.text)}

+
+
+ + ); + } +} 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 @@ +import { observable, reaction } from 'mobx'; +import ms from 'ms'; + +import { state as delayAppState } from '../delayApp'; +import { gaEvent, gaPage } from '../../lib/analytics'; + +export { default as Component } from './Component'; + +const debug = require('debug')('Franz:feature:shareFranz'); + +const defaultState = { + isModalVisible: false, + lastShown: null, +}; + +export const state = observable(defaultState); + +export default function initialize(stores) { + debug('Initialize shareFranz feature'); + + window.franz.features.shareFranz = { + state, + }; + + function showModal() { + debug('Showing share window'); + + state.isModalVisible = true; + + gaEvent('Share Franz', 'show'); + gaPage('/share-modal'); + } + + reaction( + () => stores.user.isLoggedIn, + () => { + setTimeout(() => { + if (stores.settings.stats.appStarts % 30 === 0) { + if (delayAppState.isDelayAppScreenVisible) { + debug('Delaying share modal by 5 minutes'); + setTimeout(() => showModal(), ms('5m')); + } else { + showModal(); + } + } + }, ms('2s')); + }, + { + fireImmediately: true, + }, + ); +} 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 @@ "feature.delayApp.text": "Franz will continue in {seconds} seconds.", "premiumFeature.button.upgradeAccount": "Upgrade account", "app.errorHandler.headline": "Something went wrong", - "app.errorHandler.action": "Reload" + "app.errorHandler.action": "Reload", + "feature.shareFranz.headline": "Franz is better together!", + "feature.shareFranz.text": "Tell your friends and colleagues how awesome Franz is and help us to spread the word.", + "feature.shareFranz.action.email": "Send as email", + "feature.shareFranz.action.facebook": "Share on Facebook", + "feature.shareFranz.action.twitter": "Share on Twitter", + "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", + "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" } 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'; import spellchecker from '../features/spellchecker'; import serviceProxy from '../features/serviceProxy'; import basicAuth from '../features/basicAuth'; +import shareFranz from '../features/shareFranz'; import { DEFAULT_FEATURES_CONFIG } from '../config'; @@ -56,5 +57,6 @@ export default class FeaturesStore extends Store { spellchecker(this.stores, this.actions); serviceProxy(this.stores, this.actions); basicAuth(this.stores, this.actions); + shareFranz(this.stores, this.actions); } } -- cgit v1.2.3-70-g09d2