diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index fcc4118af..da0035b9e 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -2348,5 +2348,40 @@
},
"settings": {
"message": "Settings"
+ },
+ "try_ghostery_midnight": {
+ "message": "Try Ghostery Midnight"
+ },
+ "seven_day_free_trial": {
+ "message": "7 Day Free Trial ($$14/mo)",
+ "description": "Do not localize currency. Use the $14 USD amount. The second $ is needed to escape the special meaning of $"
+ },
+ "full_coverage_protection_promise": {
+ "message": "Get full-coverage protection across all browsers & apps on your device"
+ },
+ "system_wide_tracker_and_ad_blocking": {
+ "message": "System-wide tracker & ad-blocking"
+ },
+ "built_in_vpn": {
+ "message": "Built-in VPN"
+ },
+ "custom_whitelist_options": {
+ "message": "Custom whitelist options"
+ },
+ "historical_tracking_insights": {
+ "message": "Historical tracking insights"
+ },
+ "download_for_free": {
+ "message": "Download for free"
+ },
+ "support_ghostery_for_2_instead": {
+ "message": "Support Ghostery for $$2/mo instead",
+ "description": "Do not localize currency. Use the $2 USD amount. The second $ is needed to escape the special meaning of $"
+ },
+ "no_thanks_continue_with_basic": {
+ "message": "No thanks, continue with basic"
+ },
+ "no_thanks_turn_promos_off": {
+ "message": "No thanks, turn promos off"
}
}
diff --git a/app/hub/Views/HomeView/HomeViewActions.js b/app/hub/Views/HomeView/HomeViewActions.js
index 3de1b1976..026bf2011 100644
--- a/app/hub/Views/HomeView/HomeViewActions.js
+++ b/app/hub/Views/HomeView/HomeViewActions.js
@@ -12,7 +12,7 @@
*/
import { log, sendMessageInPromise } from '../../utils';
-import { GET_HOME_PROPS, MARK_PLUS_PROMO_MODAL_SHOWN, SET_METRICS } from './HomeViewConstants';
+import { GET_HOME_PROPS, MARK_PREMIUM_PROMO_MODAL_SHOWN, SET_METRICS } from './HomeViewConstants';
export function getHomeProps() {
return function(dispatch) {
@@ -40,8 +40,8 @@ export function setMetrics(actionData) {
};
}
-export function markPlusPromoModalShown() {
+export function markPremiumPromoModalShown() {
return {
- type: MARK_PLUS_PROMO_MODAL_SHOWN,
+ type: MARK_PREMIUM_PROMO_MODAL_SHOWN,
};
}
diff --git a/app/hub/Views/HomeView/HomeViewConstants.js b/app/hub/Views/HomeView/HomeViewConstants.js
index 2defff14b..d4040d1eb 100644
--- a/app/hub/Views/HomeView/HomeViewConstants.js
+++ b/app/hub/Views/HomeView/HomeViewConstants.js
@@ -13,5 +13,5 @@
// Home View
export const GET_HOME_PROPS = 'GET_HOME_PROPS';
-export const MARK_PLUS_PROMO_MODAL_SHOWN = 'MARK_PLUS_PROMO_MODAL_SHOWN';
+export const MARK_PREMIUM_PROMO_MODAL_SHOWN = 'MARK_PREMIUM_PROMO_MODAL_SHOWN';
export const SET_METRICS = 'SET_METRICS';
diff --git a/app/hub/Views/HomeView/HomeViewContainer.jsx b/app/hub/Views/HomeView/HomeViewContainer.jsx
index b9edcd753..f7ee124e5 100644
--- a/app/hub/Views/HomeView/HomeViewContainer.jsx
+++ b/app/hub/Views/HomeView/HomeViewContainer.jsx
@@ -15,7 +15,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import QueryString from 'query-string';
import HomeView from './HomeView';
-import { PlusPromoModal } from '../../../shared-components';
+import { PremiumPromoModal } from '../../../shared-components';
import { sendMessage } from '../../utils';
import globals from '../../../../src/classes/Globals';
@@ -41,8 +41,7 @@ class HomeViewContainer extends Component {
props.actions.getHomeProps();
- // Prevent flickering in of user's email if getUser() returns after initial render,
- // as well as flickering of plus promo modal if user is already a subscriber
+ // Prevent flickering in of user's email if getUser() returns after initial render
props.actions.getUser()
.then(() => {
this.setState({
@@ -61,29 +60,57 @@ class HomeViewContainer extends Component {
}
/**
+ * Handle clicks on premium promo modal button
+ * @param type 'basic' (default), 'plus', or 'premium'
* @private
- * Function to handle clicks on Select Basic in the Plus Promo Modal
*/
- _handlePromoSelectBasicClick = () => {
+ _handlePremiumPromoModalClick = (type = 'basic') => {
// GH-1777
- // we want to show the Plus Promo modal once per Hub visit
- this.props.actions.markPlusPromoModalShown();
-
- sendMessage('SET_PLUS_PROMO_MODAL_SEEN', {});
+ // we want to show the promo modal exactly once per Hub visit
+ this.props.actions.markPremiumPromoModalShown();
+
+ sendMessage('SET_PREMIUM_PROMO_MODAL_SEEN', {});
+
+ switch (type) {
+ case 'plus':
+ window.open(`https://checkout.${DOMAIN}.com/plus?utm_source=gbe&utm_campaign=intro_hub`, '_blank');
+ break;
+ case 'premium':
+ window.open('https://ghostery.com/thanks-for-downloading-midnight', '_blank');
+ break;
+ case 'basic':
+ default:
+ break;
+ }
}
/**
* @private
- * Function to handle clicks on 'Select Plus' in the Plus Promo Modal (Choose Your Plan)
+ * Function to handle clicks on "No thanks, continue with basic" in Premium promo modal
*/
- _handlePromoSelectPlusClick = () => {
- // GH-1777
- // we want to show the Plus Promo modal once per Hub visit
- this.props.actions.markPlusPromoModalShown();
+ _handleKeepBasicClick = () => { this._handlePremiumPromoModalClick(); }
- sendMessage('SET_PLUS_PROMO_MODAL_SEEN', {});
+ /**
+ * @private
+ * Function to handle clicks on the "Get Plus instead" link in the Premium promo modal
+ */
+ _handleGetPlusClick = () => { this._handlePremiumPromoModalClick('plus'); }
+
+ /**
+ * @private
+ * Function to handle clicks on the Midnight download button in the Premium promo modal
+ */
+ _handleTryMidnightClick = () => { this._handlePremiumPromoModalClick('premium'); }
+
+ /**
+ * @returns {bool}
+ * @private
+ * Is the user a Premium subscriber?
+ */
+ _premiumSubscriber = () => {
+ const { loggedIn, user } = this.props;
- window.open(`https://checkout.${DOMAIN}.com/plus?utm_source=gbe&utm_campaign=intro_hub`, '_blank');
+ return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:premium'));
}
_render() {
@@ -91,7 +118,7 @@ class HomeViewContainer extends Component {
const { home, user } = this.props;
const isPlus = user && user.subscriptionsPlus || false;
const {
- plus_promo_modal_shown,
+ premium_promo_modal_shown,
setup_complete,
tutorial_complete,
enable_metrics,
@@ -106,15 +133,16 @@ class HomeViewContainer extends Component {
isPlus,
};
- const showPromoModal = !isPlus && !plus_promo_modal_shown;
+ const showPromoModal = !premium_promo_modal_shown && !this._premiumSubscriber();
return (
@@ -137,7 +165,7 @@ class HomeViewContainer extends Component {
HomeViewContainer.propTypes = {
home: PropTypes.shape({
enable_metrics: PropTypes.bool,
- plus_promo_modal_shown: PropTypes.bool,
+ premium_promo_modal_shown: PropTypes.bool,
setup_complete: PropTypes.bool,
tutorial_complete: PropTypes.bool,
}),
@@ -148,7 +176,7 @@ HomeViewContainer.propTypes = {
actions: PropTypes.shape({
getHomeProps: PropTypes.func.isRequired,
getUser: PropTypes.func.isRequired,
- markPlusPromoModalShown: PropTypes.func.isRequired,
+ markPremiumPromoModalShown: PropTypes.func.isRequired,
setMetrics: PropTypes.func.isRequired,
}).isRequired,
};
@@ -157,7 +185,7 @@ HomeViewContainer.propTypes = {
HomeViewContainer.defaultProps = {
home: {
enable_metrics: false,
- plus_promo_modal_shown: false,
+ premium_promo_modal_shown: false,
setup_complete: false,
tutorial_complete: false,
},
diff --git a/app/hub/Views/HomeView/HomeViewReducer.js b/app/hub/Views/HomeView/HomeViewReducer.js
index f60c13123..1582d7e62 100644
--- a/app/hub/Views/HomeView/HomeViewReducer.js
+++ b/app/hub/Views/HomeView/HomeViewReducer.js
@@ -11,7 +11,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/
-import { GET_HOME_PROPS, MARK_PLUS_PROMO_MODAL_SHOWN, SET_METRICS } from './HomeViewConstants';
+import { GET_HOME_PROPS, MARK_PREMIUM_PROMO_MODAL_SHOWN, SET_METRICS } from './HomeViewConstants';
const initialState = {};
@@ -31,10 +31,10 @@ function HomeViewReducer(state = initialState, action) {
}),
});
}
- case MARK_PLUS_PROMO_MODAL_SHOWN: {
+ case MARK_PREMIUM_PROMO_MODAL_SHOWN: {
return Object.assign({}, state, {
home: Object.assign({}, state.home, {
- plus_promo_modal_shown: true,
+ premium_promo_modal_shown: true,
})
});
}
diff --git a/app/images/panel/midnight-beta-icon.svg b/app/images/panel/midnight-beta-icon.svg
new file mode 100644
index 000000000..6621b5a68
--- /dev/null
+++ b/app/images/panel/midnight-beta-icon.svg
@@ -0,0 +1,6 @@
+
diff --git a/app/images/panel/midnight-check-icon.svg b/app/images/panel/midnight-check-icon.svg
new file mode 100644
index 000000000..ba6251149
--- /dev/null
+++ b/app/images/panel/midnight-check-icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/app/images/panel/midnight-logo.svg b/app/images/panel/midnight-logo.svg
new file mode 100644
index 000000000..28cba2896
--- /dev/null
+++ b/app/images/panel/midnight-logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/panel/components/InsightsPromoModal.jsx b/app/panel/components/InsightsPromoModal.jsx
index 7936ad4cc..5b1e54b7e 100644
--- a/app/panel/components/InsightsPromoModal.jsx
+++ b/app/panel/components/InsightsPromoModal.jsx
@@ -22,7 +22,7 @@ const INSIGHTS = 'insights';
* @memberof PanelClasses
*/
class InsightsPromoModal extends React.Component {
- handleNoThanksClick = () => { this.props.handleNoThanksClick(INSIGHTS); }
+ handleGoAwayClick = () => { this.props.handleGoAwayClick(INSIGHTS); }
handleSubscribeClick = () => { this.props.handleSubscribeClick(INSIGHTS); }
@@ -78,7 +78,7 @@ class InsightsPromoModal extends React.Component {
{t('subscribe_pitch_sign_in')}
- {t('no_thanks_turn_promos_off')}
+ {t('no_thanks_turn_promos_off')}
diff --git a/app/panel/components/Panel.jsx b/app/panel/components/Panel.jsx
index 575027e6f..d583a2c9f 100644
--- a/app/panel/components/Panel.jsx
+++ b/app/panel/components/Panel.jsx
@@ -14,9 +14,8 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import Header from '../containers/HeaderContainer';
-import { PlusPromoModal } from '../../shared-components';
+import { PremiumPromoModal } from '../../shared-components';
import InsightsPromoModal from './InsightsPromoModal';
-import PlusUpgradePromoModal from './PlusUpgradePromoModal';
import { DynamicUIPortContext } from '../contexts/DynamicUIPortContext';
import { sendMessage } from '../utils/msg';
import { setTheme } from '../utils/utils';
@@ -200,15 +199,18 @@ class Panel extends React.Component {
return false;
}
- _handlePromoNoThanksClick = (modal) => {
+ /**
+ * @param modal 'insights' or 'premium'
+ * @private
+ * Handle clicks on the link to turn off promos in the promo modals
+ */
+ _handlePromoGoAwayClick = (modal) => {
this.props.actions.togglePromoModal();
sendMessage('promoModals.turnOffPromos', {});
if (modal === 'insights') {
sendMessage('ping', 'promo_modals_decline_insights_upgrade');
- } else if (modal === 'plus_upgrade') {
- sendMessage('ping', 'promo_modals_decline_plus_upgrade');
}
this.props.actions.showNotification({
@@ -218,6 +220,10 @@ class Panel extends React.Component {
});
};
+ /**
+ * @private
+ * Handle clicks on sign in links in promo modals
+ */
_handlePromoSignInClick = () => {
this.props.actions.togglePromoModal();
history.push({
@@ -225,25 +231,27 @@ class Panel extends React.Component {
});
};
- _handlePromoSelectBasicClick = () => {
+ /**
+ * @private
+ * Handle clicks on the download button in the Premium promo modals
+ */
+ _handlePromoTryMidnightClick = () => {
this.props.actions.togglePromoModal();
- // we do not mark the choice-required initial plus promo as 'seen' until
- // the user has clicked Select Basic or Select Plus
- sendMessage('promoModals.sawPlusPromo', {});
- };
+ const url = 'https://ghostery.com/thanks-for-downloading-midnight';
+ sendMessage('openNewTab', {
+ url,
+ become_active: true,
+ });
+ }
/**
* @private
- * Handle clicks on 'Select Plus' from the Plus Promo Modal (Choose Your Plan)
+ * Handle clicks on the 'Get Plus' option in the Premium modals
*/
- _handlePromoSelectPlusClick = () => {
+ _handlePromoGetPlusClick = () => {
this.props.actions.togglePromoModal();
- // we do not mark the choice-required initial plus promo as 'seen' until
- // the user has clicked Select Basic or Select Plus
- sendMessage('promoModals.sawPlusPromo', {});
-
const url = `https://checkout.${DOMAIN}.com/plus?utm_source=gbe&utm_campaign=in_app`;
sendMessage('openNewTab', {
url,
@@ -253,8 +261,7 @@ class Panel extends React.Component {
/**
* @private
- * Handle click action when user selects Subscribe button in
- * Plus Upgrade or Insights modal
+ * Handle click action when user selects Subscribe button in the Insights modal
* @param {string} modal Modal type (insights or plus)
*/
_handlePromoSubscribeClick = (modal) => {
@@ -265,9 +272,6 @@ class Panel extends React.Component {
if (modal === 'insights') {
sendMessage('ping', 'promo_modals_insights_upgrade_cta');
url += 'insights?utm_source=gbe&utm_campaign=in_app_upgrade';
- } else if (modal === 'plus_upgrade') {
- sendMessage('ping', 'promo_modals_plus_upgrade_cta');
- url += 'plus?utm_source=gbe&utm_campaign=in_app_upgrade';
}
sendMessage('openNewTab', {
@@ -276,57 +280,68 @@ class Panel extends React.Component {
});
};
+ /**
+ * @param modal 'insights' or 'premium'
+ * @private
+ * Handle clicks on the 'X' close icon in promo modals
+ */
_handlePromoXClick = (modal) => {
this.props.actions.togglePromoModal();
if (modal === 'insights') {
sendMessage('ping', 'promo_modals_decline_insights_upgrade');
- } else if (modal === 'plus_upgrade') {
- sendMessage('ping', 'promo_modals_decline_plus_upgrade');
}
};
- _plusSubscriber = () => {
+ /**
+ * @returns {bool}
+ * @private
+ * Is the user an Insights subscriber?
+ */
+ _insightsSubscriber = () => {
const { loggedIn, user } = this.props;
- return loggedIn && (user && user.subscriptionsPlus);
+ return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:insights'));
}
- _insightsSubscriber = () => {
+ /**
+ * @returns {bool}
+ * @private
+ * Is the user a Premium subscriber?
+ */
+ _premiumSubscriber = () => {
const { loggedIn, user } = this.props;
- return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:insights'));
+ return loggedIn && (user && user.scopes && user.scopes.includes('subscriptions:premium'));
}
- _renderPlusPromoModal = () => {
- if (this._plusSubscriber() || this._insightsSubscriber()) return null;
+ /**
+ * @returns {JSX}
+ * @private
+ * Renders the Premium promo modal
+ */
+ _renderPremiumPromoModal = () => {
+ if (this._premiumSubscriber()) return null;
- if (this.props.promoModal === 'plus_upgrade') {
- // the upgrade promo does not require the user to make a choice, so we mark it as 'seen' immediately
- sendMessage('promoModals.sawPlusPromo', {});
- sendMessage('ping', 'promo_modals_show_upgrade_plus');
- return (
-
- );
- }
+ sendMessage('promoModals.sawPremiumPromo', {});
- // promoModal === 'plus_initial'
return (
-
);
}
+ /**
+ * @returns {null|JSX}
+ * @private
+ * Renders the Insights promo modal if the user is not already an Insights subscriber
+ */
_renderInsightsPromoModal = () => {
if (this._insightsSubscriber()) return null;
@@ -336,7 +351,7 @@ class Panel extends React.Component {
return (
{
const {
promoModal,
@@ -356,8 +376,8 @@ class Panel extends React.Component {
return this._renderInsightsPromoModal();
}
- if (promoModal === 'plus_initial' || promoModal === 'plus_upgrade') {
- return this._renderPlusPromoModal();
+ if (promoModal === 'premium') {
+ return this._renderPremiumPromoModal();
}
return null;
diff --git a/app/panel/components/PlusUpgradePromoModal.jsx b/app/panel/components/PlusUpgradePromoModal.jsx
deleted file mode 100644
index e4e5fd456..000000000
--- a/app/panel/components/PlusUpgradePromoModal.jsx
+++ /dev/null
@@ -1,81 +0,0 @@
-/**
- * Plus Upgrade Promo Modal Component
- *
- * Ghostery Browser Extension
- * https://www.ghostery.com/
- *
- * Copyright 2019 Ghostery, Inc. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0
- */
-
-import React from 'react';
-import ClassNames from 'classnames';
-import Modal from '../../shared-components/Modal';
-import ModalExitButton from './BuildingBlocks/ModalExitButton';
-
-const PLUS_UPGRADE = 'plus_upgrade';
-
-/**
- * @class Implements the Upgrade variant of the Plus Promo Modal
- * @memberof PanelClasses
- */
-class PlusUpgradePromoModal extends React.Component {
- handleNoThanksClick = () => { this.props.handleNoThanksClick(PLUS_UPGRADE); }
-
- handleSubscribeClick = () => { this.props.handleSubscribeClick(PLUS_UPGRADE); }
-
- handleXClick = () => { this.props.handleXClick(PLUS_UPGRADE); }
-
- render() {
- const { loggedIn } = this.props;
-
- const contentClassNames = ClassNames(
- 'PlusPromoModal__content',
- 'flex-container',
- 'flex-dir-column',
- 'align-middle',
- 'panel',
- 'upgrade'
- );
-
- // TODO update ModalExitButton class here
- // TODO pass down handler for click on ModalExitButton
- return (
-
-
-
-

-
- {t('upgrade_your_ghostery_experience')}
-
-
-
-
-
- {t('upgrade_to_plus')}
-
-
-
- {
- !loggedIn &&
- (
-
- {t('already_subscribed_sign_in')}
-
- )
- }
-
- {t('no_thanks_turn_promos_off')}
-
-
-
-
-
- );
- }
-}
-
-export default PlusUpgradePromoModal;
diff --git a/app/scss/hub.scss b/app/scss/hub.scss
index a59365f96..9cc1a696b 100644
--- a/app/scss/hub.scss
+++ b/app/scss/hub.scss
@@ -76,7 +76,7 @@ html, body, #root {
// Imports from ../shared-components directory
@import '../shared-components/ExitButton/ExitButton.scss';
@import '../shared-components/Modal/Modal.scss';
-@import '../shared-components/PlusPromoModal/PlusPromoModal.scss';
+@import '../shared-components/PremiumPromoModal/PremiumPromoModal.scss';
@import '../shared-components/SteppedNavigation/SteppedNavigation.scss';
@import '../shared-components/ToastMessage/ToastMessage.scss';
@import '../shared-components/ToggleCheckbox/ToggleCheckbox.scss';
diff --git a/app/scss/panel.scss b/app/scss/panel.scss
index 3f6596ed4..eaef66ef7 100644
--- a/app/scss/panel.scss
+++ b/app/scss/panel.scss
@@ -79,4 +79,4 @@ html body {
// Imports from ../shared-components directory
@import '../shared-components/Modal/Modal.scss';
-@import '../shared-components/PlusPromoModal/PlusPromoModal.scss';
+@import '../shared-components/PremiumPromoModal/PremiumPromoModal.scss';
diff --git a/app/shared-components/PlusPromoModal/PlusPromoModal.jsx b/app/shared-components/PlusPromoModal/PlusPromoModal.jsx
deleted file mode 100644
index a85c5bf0a..000000000
--- a/app/shared-components/PlusPromoModal/PlusPromoModal.jsx
+++ /dev/null
@@ -1,174 +0,0 @@
-/**
- * Plus Promo Modal Component
- *
- * Ghostery Browser Extension
- * https://www.ghostery.com/
- *
- * Copyright 2019 Ghostery, Inc. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0
- */
-
-import React from 'react';
-import PropTypes from 'prop-types';
-import ClassNames from 'classnames';
-import Modal from '../Modal/Modal';
-
-/**
- * A functional React component for a Plus Promo Modal that may be displayed in the Hub and/or Panel
- * @return {JSX} JSX for rendering a Plus Promo Modal
- * @memberof SharedComponents
- */
-const PlusPromoModal = (props) => {
- const {
- show,
- location,
- handleSelectBasicClick,
- handleSelectPlusClick,
- handleSignInClick,
- } = props;
-
- const isInHub = location === 'hub';
- const isInPanel = location === 'panel';
-
- const locationClassName = {
- 'in-hub': isInHub,
- 'in-panel': location === 'panel'
- };
- const contentClassNames = ClassNames(
- 'PlusPromoModal__content',
- 'flex-container',
- 'flex-dir-column',
- 'align-middle',
- 'initial',
- locationClassName
- );
- const optionsContainerClassNames = ClassNames(
- 'PlusPromoModal__options-container',
- 'full-width',
- locationClassName
- );
- const optionsDecriptionClassNames = ClassNames(
- 'PlusPromoModal__option-description-item',
- locationClassName
- );
- const chooseYourPlanClassNames = ClassNames(
- 'PlusPromoModal__choose-your-plan',
- locationClassName
- );
- const recommendedBannerClassNames = ClassNames(
- 'PlusPromoModal__recommended-banner',
- locationClassName
- );
- const optionDescriptionBoxClassNames = ClassNames(
- 'PlusPromoModal__option-description-box',
- locationClassName
- );
- const buttonBackgroundClassNames = ClassNames(
- 'PlusPromoModal__buttons-background',
- 'initial',
- locationClassName
- );
-
- return (
-
-
-
- {isInHub && (
-
- {t('ghostery_is_ready')}
-
- )}
-
- {isInHub ? t('choose_your_privacy_plan') : t('choose_your_ghostery_privacy_plan')}
-
-
-
-
-
-
{t('ghostery_basic')}
-
-
- 0
-
- {t('per_month')}
-
-
-
{t('faster_cleaner_browsing')}
-
{t('blocks_ads')}
-
{t('blocks_trackers')}
-
{t('data_protection')}
-
-
-
-
-
-
-

-
{t('recommended')}
-
-
{t('ghostery_plus')}
-
-
- {t('plus_monthly_subscription_price_number')}
-
- {t('per_month')}
-
-
-
{t('all_basic_features_plus_COLON')}
-
-
-

- {t('historical_tracker_stats')}
-
-
-
-
-

- {t('priority_support')}
-
-
-
-
-

- {t('new_color_themes')}
-
-
-
-
-
-
-
-
- {t('select_basic')}
-
-
- {t('select_plus')}
-
- {isInPanel && (
-
- {t('already_subscribed_sign_in')}
-
- )}
-
-
-
- );
-};
-
-// PropTypes ensure we pass required props of the correct type
-PlusPromoModal.propTypes = {
- show: PropTypes.bool.isRequired,
- location: PropTypes.string.isRequired,
- handleSelectBasicClick: PropTypes.func.isRequired,
- handleSelectPlusClick: PropTypes.func.isRequired,
- handleSignInClick: PropTypes.func,
-};
-
-PlusPromoModal.defaultProps = {
- handleSignInClick: () => {},
-};
-
-export default PlusPromoModal;
diff --git a/app/shared-components/PlusPromoModal/PlusPromoModal.scss b/app/shared-components/PlusPromoModal/PlusPromoModal.scss
deleted file mode 100644
index 897a73d0d..000000000
--- a/app/shared-components/PlusPromoModal/PlusPromoModal.scss
+++ /dev/null
@@ -1,320 +0,0 @@
-/**
- * Plus Promo Modal Sass
- *
- * Ghostery Browser Extension
- * https://www.ghostery.com/
- *
- * Copyright 2019 Ghostery, Inc. All rights reserved.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0
- */
-
-// Plus Promo Modal
-$standard-font-family: Roboto, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
-$condensed-font-family: Roboto Condensed, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
-
-.PlusPromoModal__gold-ghostie-badge {
- margin-top: 21px;
-}
-
-.PlusPromoModal__header {
- font-size: 20px;
- font-weight: bold;
- font-family: $standard-font-family;
- padding: 0 15px;
- text-align: center;
-}
-
-.PlusPromoModal__description {
- width: 350px;
- text-align: center;
- font-size: 18px;
- font-weight: 500;
- font-family: $standard-font-family
-}
-
-.PlusPromoModal__content {
- position: relative;
- background-color: #f7f7f7;
- border: 1.9px solid #930194;
- justify-content: space-between;
- z-index: 10;
- &.initial {
- &.in-hub {
- width: 646px;
- height: 553px;
- }
- &.in-panel {
- width: 556px;
- height: 471px;
- }
- }
- &.upgrade {
- width: 475px;
- min-height: 425px;
- }
-}
-
-.PlusPromoModal__buttons-background {
- background-color: #e7ecee;
- margin-bottom: 3px;
- width: 99%;
- height: 72px;
- z-index: -1;
- display: flex;
- &.initial {
- height: 72px;
- flex-direction: row;
- justify-content: space-around;
- align-items: center;
- &.in-panel {
- height: 80px;
- flex-wrap: wrap;
- align-content: center;
- }
- }
- &.upgrade {
- height: 95px;
- flex-direction: column;
- justify-content: center;
- }
-}
-
-.PlusPromoModal__thanks-for-download {
- padding-top: 30px;
- font-size: 26px;
- font-weight: bold;
- text-align: center;
-}
-
-.PlusPromoModal__choose-your-plan {
- font-size: 18px;
- font-weight: bold;
- font-family: $standard-font-family;
- color: #4a4a4a;
- text-align: center;
- &.in-hub {margin-top: 10px;}
- &.in-panel {
- margin-bottom: 10px;
- padding-top: 17px;
- }
-}
-
-.PlusPromoModal__options-container {
- display: flex;
- justify-content: space-around;
- &.in-hub {}
- &.in-panel {}
-}
-
-.PlusPromoModal__option-container {
- display: flex;
- align-items: center;
- flex-direction: column;
-}
-
-.PlusPromoModal__option-description-box {
- border-style: solid;
- border-width: 3px;
- background-color: #FFFFFF;
- padding: 0 15px;
-
- &.basic {
- border-color: #1dafed;
- }
-
- &.plus {
- position: relative; // so that the recommended banner is positioned relative to this element
- border-image-source: linear-gradient(39deg, #f2daa2, #eab968);
- border-image-slice: 1;
- }
-
- &.in-hub {
- width: 240px;
- flex: 1;
- min-height: 275px;
- }
-
- &.in-panel {
- width: 221px;
- flex: 1;
- min-height: 250px;
- }
-}
-
-.PlusPromoModal__recommended-banner {
- position: absolute;
- font-family: $standard-font-family;
-
- &.in-hub {
- left: -30px;
- top: -30px;
- }
-
- &.in-panel {
- left: -30px;
- top: -32px;
- }
-}
-
-.PlusPromoModal__recommended-banner-text {
- position: relative;
- left: 15px;
- top: -47px;
- transform: rotate(-15deg);
- color: white;
- font-size: 16px;
- font-weight: bold;
-}
-
-.PlusPromoModal__option-header {
- margin-top: 20px;
- font-family: $condensed-font-family;
- font-size: 19px;
- font-weight: bold;
- text-align: center;
-
- &.basic { color: #1dafed; }
- &.plus { color: #ebbf73; }
-}
-
-.PlusPromoModal__price-text {
- line-height: 1.3;
- text-align: center;
-
- &.basic { color: #1dafed; }
- &.plus { color: #ebbf73; }
-}
-
-.PlusPromoModal__currency-sign {
- font-family: $condensed-font-family;
- font-size: 30px;
- font-weight: bold;
-}
-
-.PlusPromoModal__amount {
- vertical-align: middle;
- font-family: $standard-font-family;
- font-size: 58px;
- font-weight: normal;
-}
-
-.PlusPromoModal__per-month {
- vertical-align: sub;
- font-family: $condensed-font-family;
- font-size: 18px;
- font-weight: bold;
-}
-
-.PlusPromoModal__option-description-item {
- padding-bottom: 15px;
- text-align: center;
- font-family: $standard-font-family;
- font-size: 16px;
- color: #333333;
- line-height: 16px;
- &.in-panel {
- padding-bottom: 10px;
- font-size: 15px;
- }
- &.italic {
- font-style: italic;
- }
-}
-
-.PlusPromoModal__check-icon {
- padding-right: 10px;
-}
-
-.PlusPromoModal__plus-option-description-item-container {
- display: flex;
- justify-content: center;
-}
-
-.PlusPromoModal__button-container {
- display: flex;
- margin-bottom: 10px;
- align-self: center;
-}
-
-.PlusPromoModal__button {
- cursor: pointer;
- display: flex;
- justify-content: center;
- align-items: center;
- border-radius: 3px;
- font-weight: bold;
- font-family: $condensed-font-family;
- letter-spacing: 0.5px;
- text-transform: uppercase;
- cursor: pointer;
- transition: background-color 0.25s ease-out, color 0.25s ease-out;
- -webkit-appearance: none;
-
- &.basic {
- height: 40px;
- font-size: 14px;
- border: solid 2px #15b4f2;
- background-color: white;
- color: #2cbcf4;
- box-shadow: none;
- }
- &.basic:hover {
- background-color: #2cbcf4;
- color: white;
- }
-
- &.plus {
- font-size: 14px;
- height: 38px;
- border: none;
- background-image: linear-gradient(to bottom, #2fdbfa, #15b4f2);
- color: white;
- box-shadow: none;
- }
- &.plus:hover {
- background-image: linear-gradient(to bottom, #1fcbea, #05a4e2);
- }
-
- &.upgrade {
- font-size: 13px;
- height: 36px;
- box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12);
- background-color: #1dafed;
- color: white;
- }
- &.upgrade:hover {
- background-color: #0698d6;
- }
-
- .side-padded {
- padding: 0px 20px 0px; // more localization-proof than hardcoding width
- }
-
- .button-text {
- color: white;
- text-decoration: none;
- }
-}
-
-.PlusPromoModal__text-link-container {
- display: flex;
- justify-content: space-evenly;
-}
-
-.PlusPromoModal__text-link {
- margin-left: 10px;
- margin-right: 10px;
- font-size: 13px;
- font-family: $standard-font-family;
- color: #4a4a4a;
- text-decoration: underline;
- text-align: center;
- cursor: pointer;
- &.sign-in {
- margin-top: 5px;
- width: 100%;
- }
-}
diff --git a/app/shared-components/PremiumPromoModal/PremiumPromoModal.jsx b/app/shared-components/PremiumPromoModal/PremiumPromoModal.jsx
new file mode 100644
index 000000000..d7163fb3c
--- /dev/null
+++ b/app/shared-components/PremiumPromoModal/PremiumPromoModal.jsx
@@ -0,0 +1,139 @@
+/**
+ * Premium Promo Modal Component
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+import Modal from '../Modal/Modal';
+import ModalExitButton from '../../panel/components/BuildingBlocks/ModalExitButton';
+
+/**
+ * A functional React component for a Premium Promo Modal that may be displayed in the Hub and/or Panel
+ * @return {JSX} JSX for rendering a Premium Promo Modal
+ * @memberof SharedComponents
+ */
+const PremiumPromoModal = (props) => {
+ const {
+ show,
+ location,
+ handleTryMidnightClick,
+ handleGetPlusClick,
+ handleKeepBasicClick,
+ handleGoAwayClick,
+ handleXClick,
+ } = props;
+
+ const isInHub = location === 'hub';
+ const isInPanel = location === 'panel';
+
+ const contentClassNames = ClassNames(
+ 'PremiumPromoModal__content',
+ 'flex-container',
+ 'flex-dir-column',
+ 'align-middle',
+ );
+
+ return (
+
+
+ {isInPanel && (
+
+ )}
+
+
+
+
{t('try_ghostery_midnight')}
+
+
+
{t('seven_day_free_trial')}
+
+
+ {t('full_coverage_protection_promise')}
+
+
+
+
+
+
+ {t('system_wide_tracker_and_ad_blocking')}
+
+
+
+
+
+ {t('built_in_vpn')}
+
+
+
+
+
+
+
+ {t('custom_whitelist_options')}
+
+
+
+
+
+ {t('historical_tracking_insights')}
+
+
+
+
+
+
+
+ {t('download_for_free')}
+
+
+
+
+ {t('support_ghostery_for_2_instead')}
+
+ {isInHub && (
+
+ {t('no_thanks_continue_with_basic')}
+
+ )}
+ {isInPanel && (
+
+ {t('no_thanks_turn_promos_off')}
+
+ )}
+
+
+
+
+ );
+};
+
+
+// PropTypes ensure we pass required props of the correct type
+PremiumPromoModal.propTypes = {
+ show: PropTypes.bool.isRequired,
+ location: PropTypes.string.isRequired,
+ handleTryMidnightClick: PropTypes.func.isRequired,
+ handleGetPlusClick: PropTypes.func.isRequired,
+ handleKeepBasicClick: PropTypes.func,
+ handleGoAwayClick: PropTypes.func,
+ handleXClick: PropTypes.func,
+};
+
+const noop = () => {};
+PremiumPromoModal.defaultProps = {
+ handleKeepBasicClick: noop,
+ handleGoAwayClick: noop,
+ handleXClick: noop,
+};
+
+export default PremiumPromoModal;
diff --git a/app/shared-components/PremiumPromoModal/PremiumPromoModal.scss b/app/shared-components/PremiumPromoModal/PremiumPromoModal.scss
new file mode 100644
index 000000000..f8258d80c
--- /dev/null
+++ b/app/shared-components/PremiumPromoModal/PremiumPromoModal.scss
@@ -0,0 +1,166 @@
+/**
+ * Premium Promo Modal Sass
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+// Premium Promo Modal
+$standard-font-family: Roboto, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+$condensed-font-family: Roboto Condensed, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+
+.PremiumPromoModal__content {
+ position: relative;
+ z-index: 10;
+ width: 518px;
+ height: 437px;
+ border: solid 1.9px #720174;
+ background-color: #f7f7f7;
+}
+
+.PremiumPromoModal__midnight-logo {
+ width: 177px;
+ height: 106px;
+ margin: 22px 0px 16px;
+ background-image: url('/app/images/panel/midnight-logo.svg');
+}
+
+.PremiumPromoModal__header {
+ display: flex;
+ width: 100%;
+ justify-content: space-evenly;
+}
+
+.PremiumPromoModal__header-text {
+ text-align: center;
+ font-size: 20px;
+ font-weight: bold;
+ font-family: $standard-font-family;
+}
+
+.PremiumPromoModal__header-beta-icon {
+ float: right;
+ margin-left: 5px;
+ width: 20px;
+ height: 12px;
+ background-image: url('/app/images/panel/midnight-beta-icon.svg');
+}
+
+.PremiumPromoModal__sub-header {
+ margin: 9px 0px 22px;
+ width: 438px;
+ text-align: center;
+ font-size: 18px;
+ font-weight: bold;
+ line-height: 1.5;
+ color: #4A4A4A;
+}
+
+.PremiumPromoModal__features-container {
+ display: flex;
+ width: 100%;
+ margin-bottom: 28px;
+}
+
+.PremiumPromoModal__feature-column {
+ &:nth-child(odd) {
+ margin-left: 20px;
+ width: 55%;
+ }
+ &:nth-child(even) {
+ margin-left: -12px;
+ margin-right: 25px;
+ width: 45%;
+ }
+}
+
+.PremiumPromoModal__feature {
+ display: flex;
+ align-items: center;
+ &:nth-child(1) {
+ margin-bottom: 20px;
+ }
+}
+
+.PremiumPromoModal__feature-text {
+ font-family: $standard-font-family;
+ font-size: 14px;
+ color: $medium-gray;
+ padding-bottom: 5px;
+ line-height: 16px;
+}
+
+.PremiumPromoModal__checked-circle-icon {
+ flex: none;
+ height: 18px;
+ width: 18px;
+ margin-right: 8px;
+ background-image: url('/app/images/panel/midnight-check-icon.svg');
+ align-self: flex-start;
+}
+
+.PremiumPromoModal__buttons-background {
+ background-color: #e7ecee;
+ margin-bottom: 3px;
+ width: 99%;
+ height: 107px;
+ z-index: -1;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-evenly
+}
+
+.PremiumPromoModal__button-container {
+ display: flex;
+ justify-content: center;
+}
+
+.PremiumPromoModal__download-button {
+ width: 196px;
+ height: 36px;
+ border-radius: 2px;
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12);
+ background-image: linear-gradient(101deg, #720174 19%, #00aef0 100%);
+
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ font-family: $condensed-font-family;
+ font-size: 14px;
+ font-weight: bold;
+ letter-spacing: 0.5px;
+ text-align: center;
+ color: #ffffff;
+
+ transition: background-color 0.25s ease-out, color 0.25s ease-out;
+
+ &:hover {
+ background-image: linear-gradient(101deg, #A80AAB 19%, #41BCEA 100%);
+ }
+
+ text-transform: uppercase;
+}
+
+.PremiumPromoModal__text-link-container {
+ display: flex;
+ justify-content: space-between;
+}
+
+.PremiumPromoModal__text-link {
+ margin-left: 10px;
+ margin-right: 10px;
+ font-size: 15px;
+ font-family: $standard-font-family;
+ color: #4a4a4a;
+ text-decoration: underline;
+ text-align: center;
+ cursor: pointer;
+}
diff --git a/app/shared-components/PlusPromoModal/index.js b/app/shared-components/PremiumPromoModal/index.js
similarity index 68%
rename from app/shared-components/PlusPromoModal/index.js
rename to app/shared-components/PremiumPromoModal/index.js
index da26a8b7e..9d353dc6c 100644
--- a/app/shared-components/PlusPromoModal/index.js
+++ b/app/shared-components/PremiumPromoModal/index.js
@@ -1,5 +1,5 @@
/**
- * Point of entry index.js file for Plus Promo Modal Component
+ * Point of entry index.js file for Premium Promo Modal Component
*
* Ghostery Browser Extension
* https://www.ghostery.com/
@@ -11,6 +11,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0
*/
-import PlusPromoModal from './PlusPromoModal';
+import PremiumPromoModal from './PremiumPromoModal';
-export default PlusPromoModal;
+export default PremiumPromoModal;
diff --git a/app/shared-components/index.js b/app/shared-components/index.js
index a7fe2f139..8cea5f1ae 100644
--- a/app/shared-components/index.js
+++ b/app/shared-components/index.js
@@ -17,7 +17,7 @@
import ExitButton from './ExitButton';
import Modal from './Modal';
-import PlusPromoModal from './PlusPromoModal';
+import PremiumPromoModal from './PremiumPromoModal';
import SteppedNavigation from './SteppedNavigation';
import ToastMessage from './ToastMessage';
import ToggleCheckbox from './ToggleCheckbox';
@@ -26,7 +26,7 @@ import ToggleSwitch from './ToggleSwitch';
export {
ExitButton,
Modal,
- PlusPromoModal,
+ PremiumPromoModal,
SteppedNavigation,
ToastMessage,
ToggleCheckbox,
diff --git a/src/background.js b/src/background.js
index ef34dda8e..2dcd826b0 100644
--- a/src/background.js
+++ b/src/background.js
@@ -513,8 +513,8 @@ function handleRewards(name, message, callback) {
*/
function handleGhosteryHub(name, message, callback) {
switch (name) {
- case 'SET_PLUS_PROMO_MODAL_SEEN':
- promoModals.recordPlusPromoSighting();
+ case 'SET_PREMIUM_PROMO_MODAL_SEEN':
+ promoModals.recordPremiumPromoSighting();
break;
case 'SEND_PING': {
const { type } = message;
@@ -1006,8 +1006,8 @@ function onMessageHandler(request, sender, callback) {
});
return true;
}
- if (name === 'promoModals.sawPlusPromo') {
- promoModals.recordPlusPromoSighting();
+ if (name === 'promoModals.sawPremiumPromo') {
+ promoModals.recordPremiumPromoSighting();
return false;
}
if (name === 'promoModals.sawInsightsPromo') {
diff --git a/src/classes/ConfData.js b/src/classes/ConfData.js
index 050e302eb..22d73cba5 100644
--- a/src/classes/ConfData.js
+++ b/src/classes/ConfData.js
@@ -126,6 +126,7 @@ class ConfData {
_initProperty('notify_upgrade_updates', true);
_initProperty('paid_subscription', false);
_initProperty('plus_promo_modal_last_seen', 0);
+ _initProperty('premium_promo_modal_last_seen', 0);
_initProperty('rewards_accepted', false);
_initProperty('rewards_opted_in', false);
_initProperty('settings_last_imported', 0);
diff --git a/src/classes/PromoModals.js b/src/classes/PromoModals.js
index 2e202d80d..f0079429f 100644
--- a/src/classes/PromoModals.js
+++ b/src/classes/PromoModals.js
@@ -16,21 +16,19 @@ import globals from './Globals';
import panelData from './PanelData';
const DAYS_BETWEEN_PROMOS = {
- plus: globals.DEBUG ? 0.0005 : 30, // 40 seconds on staging
+ premium: globals.DEBUG ? 0.0005 : 30, // 40 seconds on staging
insights: globals.DEBUG ? 0.0005 : 30 // 40 seconds on staging
};
const WEEKLY_INSIGHTS_TARGET = globals.DEBUG ? 1 : 3;
const DAILY_INSIGHTS_TARGET = globals.DEBUG ? 7 : 3;
const MSECS_IN_DAY = 86400000; // 1000 msecs-in-sec * 60 secs-in-min * 60 mins-in-hour * 24 hours-in-day
-const PLUS = 'plus';
-const PLUS_INITIAL = 'plus_initial';
-const PLUS_UPGRADE = 'plus_upgrade';
+const PREMIUM = 'premium';
const INSIGHTS = 'insights';
const PROMO_MODAL_LAST_SEEN = 'promo_modal_last_seen';
/**
- * Static 'namespace' class for handling the business logic for the display of promo modals (Plus, Insights, etc...)
+ * Static 'namespace' class for handling the business logic for the display of promo modals (Premium, Insights, etc...)
* @memberOf BackgroundClasses
*/
class PromoModals {
@@ -41,28 +39,19 @@ class PromoModals {
* @return {string} Type of promo to show
*/
static whichPromoModalShouldWeDisplay() {
+ // The order is important
+ // Insights takes priority over Premium
if (this._isTimeForAPromo(INSIGHTS)) return INSIGHTS;
-
- if (this._isTimeForAPromo(PLUS)) {
- if (this._haveSeenInitialPlusPromo()) return PLUS_UPGRADE;
-
- return PLUS_INITIAL;
- }
-
+ if (this._isTimeForAPromo(PREMIUM)) return PREMIUM;
return null;
}
- static recordPlusPromoSighting() { this._recordPromoSighting(PLUS); }
+ static recordPremiumPromoSighting() { this._recordPromoSighting(PREMIUM); }
static recordInsightsPromoSighting() { this._recordPromoSighting(INSIGHTS); }
static turnOffPromos() { panelData.set({ notify_promotions: false }); }
- static _haveSeenInitialPlusPromo() {
- const lastSeenTime = conf[`${PLUS}_${PROMO_MODAL_LAST_SEEN}`];
- return (lastSeenTime !== 0);
- }
-
/**
* Check Conf values to determine if the enough time has
* passed for `type` modal to be displayed
@@ -72,9 +61,9 @@ class PromoModals {
static _isTimeForAPromo(type) {
if (conf.notify_promotions === false) { return false; }
- const lastSeenPlusPromo = conf[`${PLUS}_${PROMO_MODAL_LAST_SEEN}`];
+ const lastSeenPremiumPromo = conf[`${PREMIUM}_${PROMO_MODAL_LAST_SEEN}`];
const lastSeenInsightsPromo = conf[`${INSIGHTS}_${PROMO_MODAL_LAST_SEEN}`];
- const lastSeenPromo = lastSeenPlusPromo > lastSeenInsightsPromo ? lastSeenPlusPromo : lastSeenInsightsPromo;
+ const lastSeenPromo = Math.max(lastSeenPremiumPromo, lastSeenInsightsPromo);
if (type === INSIGHTS && !this._hasEngagedFrequently()) {
return false;
@@ -107,14 +96,9 @@ class PromoModals {
static _hasEngagedFrequently() {
const { engaged_daily_count } = conf.metrics || [];
- let very_engaged_days = 0;
- engaged_daily_count.forEach((count) => {
- very_engaged_days = count >= DAILY_INSIGHTS_TARGET ? ++very_engaged_days : very_engaged_days;
- });
-
- if (very_engaged_days >= WEEKLY_INSIGHTS_TARGET) return true;
+ const very_engaged_days = engaged_daily_count.reduce((acc, count) => (count >= DAILY_INSIGHTS_TARGET ? acc++ : acc), 0);
- return false;
+ return very_engaged_days >= WEEKLY_INSIGHTS_TARGET;
}
}