diff --git a/_locales/de/messages.json b/_locales/de/messages.json index 056553fd0..01f74b3f4 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -731,9 +731,6 @@ "panel_detail_rewards_off": { "message": "Ghostery Rewards ist AUS" }, - "panel_detail_premium_title": { - "message": "Ghostery Premium" - }, "panel_detail_premium_text": { "message": "Funktion demnächst verfügbar." }, diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 022d1b77e..b3f1807f1 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -704,9 +704,6 @@ "panel_detail_menu_rewards_title": { "message": "Rewards" }, - "panel_detail_menu_premium_title": { - "message": "Premium" - }, "panel_detail_learn_more": { "message": "Learn More" }, @@ -737,9 +734,6 @@ "panel_detail_rewards_off": { "message": "Ghostery Rewards is OFF" }, - "panel_detail_premium_title": { - "message": "Ghostery Premium" - }, "panel_detail_premium_text": { "message": "Feature coming soon." }, @@ -1243,6 +1237,9 @@ "hub_side_navigation_home": { "message": "Home" }, + "hub_side_navigation_upgrade_plan": { + "message": "Upgrade Plan" + }, "hub_side_navigation_tutorial": { "message": "Take a Tutorial" }, @@ -1309,6 +1306,81 @@ "hub_home_plus_full_protection": { "message": "You are fully protected!" }, + "hub_upgrade_your": { + "message": "Upgrade your" + }, + "hub_upgrade_protection_plan": { + "message": "protection plan" + }, + "hub_upgrade_yearly": { + "message": "Yearly" + }, + "hub_upgrade_monthly": { + "message": "Monthly" + }, + "hub_upgrade_plan_free": { + "message": "Free" + }, + "hub_upgrade_already_protected": { + "message": "Already Protected" + }, + "hub_upgrade_basic_protection": { + "message": "Basic Protection" + }, + "hub_upgrade_basic_browser_protection": { + "message": "Basic Browser Protection" + }, + "hub_upgrade_advanced_device_protection": { + "message": "Advanced Device Protection" + }, + "hub_upgrade_maximum_browser_protection": { + "message": "Maximum Browser Protection" + }, + "hub_upgrade_to": { + "message": "Upgrade to" + }, + "hub_upgrade_scroll_down": { + "message": "See full list of features" + }, + "hub_upgrade_midnight_note": { + "message": "Must have Ghostery Midnight installed" + }, + "hub_upgrade_additional_protection": { + "message": "Additional Protection" + }, + "hub_upgrade_browser_tracker_blocking": { + "message": "Browser Tracker Blocking" + }, + "hub_upgrade_browser_ad_blocking": { + "message": "Browser Ad Blocking" + }, + "hub_upgrade_custom_blocking_preferences": { + "message": "Custom Blocking Preferences" + }, + "hub_upgrade_extension_themes": { + "message": "Extension Themes" + }, + "hub_upgrade_historical_extension_stats": { + "message": "Historical Extension Stats" + }, + "hub_upgrade_application_tracker_blocking": { + "message": "Application Tracker Blocking" + }, + "hub_upgrade_application_ad_blocking": { + "message": "Application Ad Blocking" + }, + "hub_upgrade_no_vpn_logs": { + "message": "No VPN logs" + }, + "hub_upgrade_leak_protection": { + "message": "Leak Protection" + }, + "hub_upgrade_physical_servers": { + "message": "Physical Servers" + }, + "hub_upgrade_unlimited_bandwidth": { + "message": "Unlimited Bandwidth" + }, "hub_setup_page_title": { "message": "Ghostery Hub - Setup" }, @@ -2216,9 +2288,6 @@ "ghostery_basic": { "message": "Basic" }, - "ghostery_plus": { - "message": "Ghostery Plus" - }, "dollar_sign": { "message": "$" }, @@ -2228,6 +2297,9 @@ "per_month": { "message": "per month" }, + "per_year": { + "message": "per year" + }, "faster_cleaner_browsing": { "message": "Faster, Cleaner Browsing", "description": "Appears in a non-responsive modal. Character limit (including spaces and punctuation): 27." diff --git a/app/hub/Views/HomeView/HomeView.jsx b/app/hub/Views/HomeView/HomeView.jsx index 188183ddb..91727d0b1 100644 --- a/app/hub/Views/HomeView/HomeView.jsx +++ b/app/hub/Views/HomeView/HomeView.jsx @@ -34,7 +34,8 @@ const HomeView = (props) => { enable_metrics, changeMetrics, email, - isPremium + isPremium, + sendPing, } = props; const accountHref = globals.ACCOUNT_BASE_URL; @@ -65,6 +66,13 @@ const HomeView = (props) => { 'purple-border': !isPremium }); + /** + * Sends the necessary ping to background + */ + const _sendUpgradePing = () => { + sendPing({ type: 'intro_hub_home_upgrade' }); + }; + return (
@@ -150,7 +158,7 @@ const HomeView = (props) => { {t('hub_home_plus_upgrade_text')}
- + {isPremium ? t('hub_home_plus_full_protection') : t('hub_home_plus_upgrade_button_text')}
diff --git a/app/hub/Views/HomeView/HomeView.scss b/app/hub/Views/HomeView/HomeView.scss index 1e0d4a1fc..fd556f407 100644 --- a/app/hub/Views/HomeView/HomeView.scss +++ b/app/hub/Views/HomeView/HomeView.scss @@ -16,6 +16,15 @@ padding-top: 45px; padding-bottom: 25px; color: $tundora; + .button { + &:not(.hollow):hover { + background-color: #0078CA; + } + &.hollow:hover { + color: #0078CA; + border-color: #0078CA; + } + } } .HomeView--bolded { font-weight: 700; diff --git a/app/hub/Views/HomeView/HomeViewContainer.jsx b/app/hub/Views/HomeView/HomeViewContainer.jsx index 7183d2a88..380d9e3cd 100644 --- a/app/hub/Views/HomeView/HomeViewContainer.jsx +++ b/app/hub/Views/HomeView/HomeViewContainer.jsx @@ -107,7 +107,8 @@ class HomeViewContainer extends Component { _render() { const { justInstalled } = this.state; - const { home, user } = this.props; + const { home, user, actions } = this.props; + const { sendPing } = actions; const isPlus = (user && user.plusAccess) || false; const isPremium = (user && user.premiumAccess) || false; const { @@ -117,7 +118,10 @@ class HomeViewContainer extends Component { enable_metrics, } = home; - const showPromoModal = !premium_promo_modal_shown && !isPremium; + // Flag to display promo modal (used in A/B testing) + const shouldShowPromoModal = false; + // Logic to display premium modal if it is the case that it is being shown once per hub refresh to non-premium users + const showPromoModal = shouldShowPromoModal && !premium_promo_modal_shown && !isPremium; return (
@@ -138,6 +142,7 @@ class HomeViewContainer extends Component { changeMetrics={this._handleToggleMetrics} email={user ? user.email : ''} isPremium={isPremium} + sendPing={sendPing} />
); diff --git a/app/hub/Views/HomeView/__tests__/__snapshots__/HomeView.test.jsx.snap b/app/hub/Views/HomeView/__tests__/__snapshots__/HomeView.test.jsx.snap index c1523a3f7..826091295 100644 --- a/app/hub/Views/HomeView/__tests__/__snapshots__/HomeView.test.jsx.snap +++ b/app/hub/Views/HomeView/__tests__/__snapshots__/HomeView.test.jsx.snap @@ -153,10 +153,11 @@ exports[`app/hub/Views/HomeView component Snapshot tests with react-test-rendere className="HomeView__buttonContainer columns flex-container" > hub_home_plus_upgrade_button_text @@ -324,10 +325,11 @@ exports[`app/hub/Views/HomeView component Snapshot tests with react-test-rendere className="HomeView__buttonContainer columns flex-container" > hub_home_plus_full_protection diff --git a/app/hub/Views/HomeView/index.js b/app/hub/Views/HomeView/index.js index 9a947b809..af1c83824 100644 --- a/app/hub/Views/HomeView/index.js +++ b/app/hub/Views/HomeView/index.js @@ -18,6 +18,7 @@ import HomeViewContainer from './HomeViewContainer'; import HomeViewReducer from './HomeViewReducer'; import * as HomeViewActions from './HomeViewActions'; import { getUser } from '../../../Account/AccountActions'; +import { sendPing } from '../AppView/AppViewActions'; /** * Map redux store state properties to the component's own properties. @@ -34,7 +35,7 @@ const mapStateToProps = state => ({ ...state.home, ...state.account }); * @memberof SetupContainers */ const mapDispatchToProps = dispatch => ({ - actions: bindActionCreators({ ...HomeViewActions, getUser }, dispatch), + actions: bindActionCreators({ ...HomeViewActions, getUser, sendPing }, dispatch), }); export const reducer = HomeViewReducer; diff --git a/app/hub/Views/SetupView/SetupViewContainer.jsx b/app/hub/Views/SetupView/SetupViewContainer.jsx index a025a1fec..4d606bb42 100644 --- a/app/hub/Views/SetupView/SetupViewContainer.jsx +++ b/app/hub/Views/SetupView/SetupViewContainer.jsx @@ -129,7 +129,7 @@ class SetupViewContainer extends Component {
- + {t('hub_setup_modal_button_no')}
diff --git a/app/hub/Views/SetupViews/SetupAntiSuiteView/SetupAntiSuiteViewContainer.jsx b/app/hub/Views/SetupViews/SetupAntiSuiteView/SetupAntiSuiteViewContainer.jsx index f0ed52027..42d541b67 100644 --- a/app/hub/Views/SetupViews/SetupAntiSuiteView/SetupAntiSuiteViewContainer.jsx +++ b/app/hub/Views/SetupViews/SetupAntiSuiteView/SetupAntiSuiteViewContainer.jsx @@ -36,7 +36,7 @@ class SetupAntiSuiteViewContainer extends Component { activeIndex: index, hrefPrev: `/setup/${index - 1}`, hrefNext: `/setup/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_setup_exit_flow'), diff --git a/app/hub/Views/SetupViews/SetupBlockingView/SetupBlockingViewContainer.jsx b/app/hub/Views/SetupViews/SetupBlockingView/SetupBlockingViewContainer.jsx index b50fa624f..193445ade 100644 --- a/app/hub/Views/SetupViews/SetupBlockingView/SetupBlockingViewContainer.jsx +++ b/app/hub/Views/SetupViews/SetupBlockingView/SetupBlockingViewContainer.jsx @@ -38,7 +38,7 @@ class SetupBlockingViewContainer extends Component { activeIndex: index, hrefPrev: false, hrefNext: `/setup/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: false, textNext: t('next'), textDone: t('hub_setup_exit_flow'), diff --git a/app/hub/Views/SetupViews/SetupDoneView/SetupDoneViewContainer.jsx b/app/hub/Views/SetupViews/SetupDoneView/SetupDoneViewContainer.jsx index 3ee04fcb9..5b649ea5d 100644 --- a/app/hub/Views/SetupViews/SetupDoneView/SetupDoneViewContainer.jsx +++ b/app/hub/Views/SetupViews/SetupDoneView/SetupDoneViewContainer.jsx @@ -31,8 +31,8 @@ class SetupDoneViewContainer extends Component { props.actions.setSetupNavigation({ activeIndex: index, hrefPrev: `/setup/${index - 1}`, - hrefNext: '/', - hrefDone: '/', + hrefNext: '/home', + hrefDone: '/home', textPrev: t('previous'), textNext: t('done'), textDone: t('hub_setup_exit_flow'), diff --git a/app/hub/Views/SetupViews/SetupHumanWebView/SetupHumanWebViewContainer.jsx b/app/hub/Views/SetupViews/SetupHumanWebView/SetupHumanWebViewContainer.jsx index 9657fc595..96acc7156 100644 --- a/app/hub/Views/SetupViews/SetupHumanWebView/SetupHumanWebViewContainer.jsx +++ b/app/hub/Views/SetupViews/SetupHumanWebView/SetupHumanWebViewContainer.jsx @@ -32,7 +32,7 @@ class SetupHumanWebViewContainer extends Component { activeIndex: index, hrefPrev: `/setup/${index - 1}`, hrefNext: `/setup/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_setup_exit_flow'), diff --git a/app/hub/Views/SideNavigationView/SideNavigationView.scss b/app/hub/Views/SideNavigationView/SideNavigationView.scss index 8e00c3116..7a2a31b29 100644 --- a/app/hub/Views/SideNavigationView/SideNavigationView.scss +++ b/app/hub/Views/SideNavigationView/SideNavigationView.scss @@ -126,6 +126,10 @@ a .SideNavigation__menuIcon.home, a.disabled.active .SideNavigation__menuIcon.home { background-image: buildIconHome(#ffffff); } +a .SideNavigation__menuIcon.shield, +a.disabled.active .SideNavigation__menuIcon.shield { + background-image: buildIconShield(#ffffff); +} a .SideNavigation__menuIcon.setup, a.disabled.active .SideNavigation__menuIcon.setup { background-image: buildIconSetup(#ffffff); @@ -153,6 +157,9 @@ a.disabled.active .SideNavigation__menuIcon.profile { a.disabled .SideNavigation__menuIcon.home { background-image: buildIconHome(#a9b9be); } +a.disabled .SideNavigation__menuIcon.shield { + background-image: buildIconShield(#a9b9be); +} a.disabled .SideNavigation__menuIcon.setup { background-image: buildIconSetup(#a9b9be); } diff --git a/app/hub/Views/SideNavigationView/SideNavigationViewContainer.jsx b/app/hub/Views/SideNavigationView/SideNavigationViewContainer.jsx index 77492fdcf..0fa5028e5 100644 --- a/app/hub/Views/SideNavigationView/SideNavigationViewContainer.jsx +++ b/app/hub/Views/SideNavigationView/SideNavigationViewContainer.jsx @@ -50,7 +50,8 @@ class SideNavigationViewContainer extends Component { const disableRegEx = /^(\/setup(?!\/4$))|(\/tutorial(?!\/6$))/; const menuItems = [ - { href: '/', icon: 'home', text: t('hub_side_navigation_home') }, + { href: '/home', icon: 'home', text: t('hub_side_navigation_home') }, + { href: '/', icon: 'shield', text: t('hub_side_navigation_upgrade_plan') }, { href: '/setup', icon: 'setup', text: t('customize_setup') }, { href: '/tutorial', icon: 'tutorial', text: t('hub_side_navigation_tutorial') }, { href: '/plus', icon: 'plus', text: t('get_ghostery_plus') }, diff --git a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx index fccaca98e..5313f9b07 100644 --- a/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialAntiSuiteView/TutorialAntiSuiteViewContainer.jsx @@ -31,8 +31,8 @@ class TutorialAntiSuiteViewContainer extends Component { props.actions.setTutorialNavigation({ activeIndex: index, hrefPrev: `/tutorial/${index - 1}`, - hrefNext: '/', - hrefDone: '/', + hrefNext: '/home', + hrefDone: '/home', textPrev: t('previous'), textNext: t('done'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx index 246ac22d1..5210ca461 100644 --- a/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialBlockingView/TutorialBlockingViewContainer.jsx @@ -32,7 +32,7 @@ class TutorialBlockingViewContainer extends Component { activeIndex: index, hrefPrev: `/tutorial/${index - 1}`, hrefNext: `/tutorial/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx index 407bf8fa9..59c5f0208 100644 --- a/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialLayoutView/TutorialLayoutViewContainer.jsx @@ -32,7 +32,7 @@ class TutorialLayoutViewContainer extends Component { activeIndex: index, hrefPrev: `/tutorial/${index - 1}`, hrefNext: `/tutorial/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx index b59c40b1d..9aeab240b 100644 --- a/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrackerListView/TutorialTrackerListViewContainer.jsx @@ -32,7 +32,7 @@ class TutorialTrackerListViewContainer extends Component { activeIndex: index, hrefPrev: `/tutorial/${index - 1}`, hrefNext: `/tutorial/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx index f48598ad0..42d8e341e 100644 --- a/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialTrustView/TutorialTrustViewContainer.jsx @@ -32,7 +32,7 @@ class TutorialTrustViewContainer extends Component { activeIndex: index, hrefPrev: `/tutorial/${index - 1}`, hrefNext: `/tutorial/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: t('previous'), textNext: t('next'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/TutorialViews/TutorialVideoView/TutorialVideoViewContainer.jsx b/app/hub/Views/TutorialViews/TutorialVideoView/TutorialVideoViewContainer.jsx index a6af3c5ce..23659b366 100644 --- a/app/hub/Views/TutorialViews/TutorialVideoView/TutorialVideoViewContainer.jsx +++ b/app/hub/Views/TutorialViews/TutorialVideoView/TutorialVideoViewContainer.jsx @@ -32,7 +32,7 @@ class TutorialVideoViewContainer extends Component { activeIndex: index, hrefPrev: false, hrefNext: `/tutorial/${index + 1}`, - hrefDone: '/', + hrefDone: '/home', textPrev: false, textNext: t('next'), textDone: t('hub_tutorial_exit_flow'), diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanView.jsx b/app/hub/Views/UpgradePlanView/UpgradePlanView.jsx new file mode 100644 index 000000000..15967b4e1 --- /dev/null +++ b/app/hub/Views/UpgradePlanView/UpgradePlanView.jsx @@ -0,0 +1,524 @@ +/** + * Upgrade Plan View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 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, { useRef, Fragment } from 'react'; +import ClassNames from 'classnames'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; +import { BASIC, PLUS, PREMIUM } from './UpgradePlanViewConstants'; +import globals from '../../../../src/classes/Globals'; + +const featureMatrixRow = (label, isBasic, isPlus, isSparkle) => ( + + + {label} + {isSparkle && + + } + + + {isBasic && + + } + + + {isPlus && + + } + + + + + +); + +const mobileFeatureMatrixRow = (label, isBasic, isPlus, isSparkle) => ( + + + + {label} + {isSparkle && + + } + + + + {isBasic ? ( + + + + ) : ( + + + + )} + {isPlus ? ( + + + + ) : ( + + + + )} + + + + + +); + +const basicCard = () => ( +
+
+
+
+
+
+

Ghostery

+
+

{t('hub_upgrade_plan_free')}

+
+ + {t('hub_upgrade_already_protected')} + +

{t('hub_upgrade_basic_protection')}

+

+ + {t('hub_upgrade_basic_browser_protection')} +

+
+
+); + +/** + * A React class component for rendering the Upgrade Plan View + * @return {JSX} JSX for rendering the Upgrade Plan View of the Hub app + * @memberof HubComponents + */ +const UpgradePlanView = (props) => { + const { + protection_level, + show_yearly_prices, + user, + actions, + } = props; + + const { + toggleMonthlyYearlyPrices, + setBasicProtection, + setPlusProtection, + setPremiumProtection + } = actions; + + const isPlus = (user && user.plusAccess) || false; + const isPremium = (user && user.premiumAccess) || false; + + const sliderClassNames = ClassNames('switch-check', { + checked: show_yearly_prices + }); + const tabsTitleBlueClassNames = ClassNames('tabs-title tabs-title-blue', { + 'is-active': protection_level === BASIC + }); + const tabsTitleGoldClassNames = ClassNames('tabs-title tabs-title-gold', { + 'is-active': protection_level === PLUS + }); + const tabsTitlePurpleClassNames = ClassNames('tabs-title tabs-title-purple', { + 'is-active': protection_level === PREMIUM + }); + const monthlyToggleLabel = ClassNames('toggle-label', { + active: !show_yearly_prices + }); + const yearlyToggleLabel = ClassNames('toggle-label', { + active: show_yearly_prices + }); + + // Clicking arrow scrolls to table + const comparisonTableRef = useRef(null); + const scrollToComparisonTable = () => { + comparisonTableRef.current.scrollIntoView({ behavior: 'smooth' }); + }; + // Clicking arrow scrolls to table for mobile view + const mobileComparisonTableRef = useRef(null); + const scrollToMobileComparisonTable = () => { + mobileComparisonTableRef.current.scrollIntoView({ behavior: 'smooth' }); + }; + + // UTM params + const signedIn = +!!user; + const subscriptionType = () => { + if (isPremium) return 'PREMIUM'; + if (isPlus) return 'SUPPORTER'; + return '-1'; + }; + // Interval is the query Param to show monthly/yearly pricing in checkout web, also used as a ping parameter + const interval = show_yearly_prices ? 'year' : 'month'; + const utmParams = `utm_source=gbe&utm_campaign=intro_hub_c_1&signedIn=${signedIn}&st=${subscriptionType()}&subscription_interval=${interval}`; + + const plusCheckoutLink = `${globals.CHECKOUT_BASE_URL}/plus?interval=${interval}&${utmParams}`; + const premiumCheckoutLink = `${globals.CHECKOUT_BASE_URL}/premium?interval=${interval}&${utmParams}`; + + const plusCTAButton = () => ( + isPlus ? ( + + {t('hub_upgrade_already_protected')} + + ) : ( + + {`${t('hub_upgrade_to')} Plus`} + + ) + ); + + const premiumCTAButton = () => ( + isPremium ? ( + + {t('hub_upgrade_already_protected')} + + ) : ( + + {`${t('hub_upgrade_to')} Premium`} + + ) + ); + + const toggleSwitch = (mobileView, secondToggle) => { + const toggleSwitchClassNames = ClassNames('small-12 text-center columns', { + 'toggle-switch-row': mobileView, + 'second-toggle': secondToggle, + }); + return ( +
+
+ {t('hub_upgrade_monthly')} + + {t('hub_upgrade_yearly')} +
+
+ ); + }; + + const plusCard = mobileView => ( +
+
+
+
+
+

Ghostery Plus

+
+ {show_yearly_prices ? ( + +

$3.99

+

{t('per_month')}

+
+

{`( $47.88 ${t('per_year')})`}

+
+
+ ) : ( + +

$4.99

+

{t('per_month')}

+
+ )} +
+ {mobileView && toggleSwitch(true)} + {plusCTAButton()} +

{t('hub_upgrade_additional_protection')}

+

+ + {t('hub_upgrade_basic_browser_protection')} +

+

+ + {t('hub_upgrade_advanced_device_protection')} +

+
+
+ ); + + const premiumCard = mobileView => ( +
+
+
+
+
+
+

Ghostery Premium

+
+ {show_yearly_prices ? ( + +

$8.99

+

{t('per_month')}

+
+

{`( $107.88 ${t('per_year')})`}

+
+
+ ) : ( + +

$11.99

+

{t('per_month')}

+
+ )} +
+ {mobileView && toggleSwitch(true)} + {premiumCTAButton()} +

+ {t('hub_upgrade_maximum_browser_protection')} +

+

+ + {t('hub_upgrade_basic_browser_protection')} +

+

+ + {t('hub_upgrade_advanced_device_protection')} +

+

+ + VPN +

+
+
+ ); + + return ( +
+
+
+
+

{`${t('hub_upgrade_your')} Ghostery ${t('hub_upgrade_protection_plan')}`}

+ {toggleSwitch()} +
+
+
+ +
+
+ {basicCard()} + {plusCard()} + {premiumCard()} +
+
+ +
+
+
+

{`${t('hub_upgrade_your')} Ghostery ${t('hub_upgrade_protection_plan')}`}

+
+
+
+
+
    +
  • {t('hub_upgrade_plan_free')}
  • +
  • Plus
  • +
  • Premium
  • +
+
+
+
+ {protection_level === BASIC && basicCard()} + {protection_level === PLUS && plusCard(true)} + {protection_level === PREMIUM && premiumCard(true)} +
+
+
+
+
{t('hub_upgrade_scroll_down')}
+
+
+
+
+
+
{t('hub_upgrade_scroll_down')}
+
+
+
+
+
+
+
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+
+ + {`- ${t('hub_upgrade_midnight_note')}`} +
+ + + + + + + + + + {featureMatrixRow(t('hub_upgrade_browser_tracker_blocking'), true, true)} + {featureMatrixRow(t('hub_upgrade_browser_ad_blocking'), true, true)} + {featureMatrixRow(t('hub_upgrade_custom_blocking_preferences'), true, true)} + {featureMatrixRow(t('hub_upgrade_extension_themes'), false, true)} + {featureMatrixRow(t('hub_upgrade_historical_extension_stats'), false, true)} + {featureMatrixRow(t('hub_upgrade_application_tracker_blocking'), false, true, true)} + {featureMatrixRow(t('hub_upgrade_application_ad_blocking'), false, true, true)} + {featureMatrixRow('VPN', false, false, true)} + {featureMatrixRow(t('hub_upgrade_no_vpn_logs'), false, false, true)} + {featureMatrixRow(`P2P ${t('support')}`, false, false, true)} + {featureMatrixRow(`IPv6 ${t('hub_upgrade_leak_protection')}`, false, false, true)} + {featureMatrixRow(t('hub_upgrade_physical_servers'), false, false, true)} + {featureMatrixRow(t('hub_upgrade_unlimited_bandwidth'), false, false, true)} + + + + + + +
+ GhosteryGhostery PlusGhostery Premium
+ + + {t('hub_upgrade_already_protected')} + + + {plusCTAButton()} + + {premiumCTAButton()} +
+
+
+
+
+ +
+ + {`- ${t('hub_upgrade_midnight_note')}`} +
+ +
+ {toggleSwitch(true, true)} +
+
+
+

{t('ghostery_basic')}

+
+
+

Plus

+
+
+

Premium

+
+
+
+
+

{t('hub_upgrade_plan_free')}

+
+
+ {show_yearly_prices ? ( +

$3.99

+ ) : ( +

$4.99

+ )} +
+
+ {show_yearly_prices ? ( +

$8.99

+ ) : ( +

$11.99

+ )} +
+
+
+
+
+
+ + + {mobileFeatureMatrixRow(t('hub_upgrade_browser_tracker_blocking'), true, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_browser_ad_blocking'), true, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_custom_blocking_preferences'), true, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_extension_themes'), false, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_historical_extension_stats'), false, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_application_tracker_blocking'), false, true, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_application_ad_blocking'), false, true, true)} + {mobileFeatureMatrixRow('VPN', false, false, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_no_vpn_logs'), false, false, true)} + {mobileFeatureMatrixRow(`P2P ${t('support')}`, false, false, true)} + {mobileFeatureMatrixRow(`IPv6 ${t('hub_upgrade_leak_protection')}`, false, false, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_physical_servers'), false, false, true)} + {mobileFeatureMatrixRow(t('hub_upgrade_unlimited_bandwidth'), false, false, true)} + +
+
+
+
+
+ + + {t('hub_upgrade_already_protected')} + + +
+
+ + {plusCTAButton()} + +
+
+ + {premiumCTAButton()} + +
+
+
+
+
+ ); +}; + +// PropTypes ensure we pass required props of the correct type +UpgradePlanView.propTypes = { + protection_level: PropTypes.string.isRequired, + show_yearly_prices: PropTypes.bool.isRequired, + user: PropTypes.shape({ + email: PropTypes.string, + plusAccess: PropTypes.bool, + premiumAccess: PropTypes.bool, + }), + actions: PropTypes.shape({ + toggleMonthlyYearlyPrices: PropTypes.func.isRequired, + setBasicProtection: PropTypes.func.isRequired, + setPlusProtection: PropTypes.func.isRequired, + setPremiumProtection: PropTypes.func.isRequired, + }).isRequired, +}; + +// Default props used on the Home View +UpgradePlanView.defaultProps = { + user: { + email: '', + plusAccess: false, + premiumAccess: false, + }, +}; + +export default UpgradePlanView; diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanView.scss b/app/hub/Views/UpgradePlanView/UpgradePlanView.scss new file mode 100644 index 000000000..2e9b3d460 --- /dev/null +++ b/app/hub/Views/UpgradePlanView/UpgradePlanView.scss @@ -0,0 +1,783 @@ +$price-blue: #00AEF0; +$price-blue-hover: #0078CA; +$blue-alt: #4caeea; +$price-purple: #611b87; +$price-purple-alt: #720174; +$price-purple-hover: #710d72; +$price-gold: #b8860b; +$price-gold-hover: #906908; +$medium-large-breakpoint: 1118px; // Break when 3 cards on the screen overflow to next line + +.font-size-36 { + font-size: rem-calc(36); +} +.font-size-12 { + font-size: rem-calc(12); +} + +.page-template-page-content-modules, +section.home-template .section.section-pricing { + .show-for-extra-large { + @include breakpoint($medium-large-breakpoint down) { + display: none; + } + } + .hide-for-extra-large { + display: none; + @include breakpoint($medium-large-breakpoint down) { + display: block; + } + } + main { + padding-bottom: 0; + } + h1 { + margin: rem-calc(40) 0 54px 0; + color: $tundora; + font-size: rem-calc(24); + font-family: Roboto; + font-stretch: condensed; + @include breakpoint($medium-large-breakpoint down) { + margin: 40px auto 22px auto; + } + } + .toggle-switch { + span { + vertical-align: middle; + font-size: 18px; + color: #cad3d5; + @include breakpoint($medium-large-breakpoint down) { + font-size: rem-calc(12); + } + &.toggle-label.active { + color: #4a4a4a; + } + } + } + .tabs { + border-radius: 4px; + border: 2px solid #6a6a6a; + } + + .tabs-content { + border: none; + background-color: $white; + @include breakpoint($medium-large-breakpoint down) { + margin-top: 32px; + } + } + + .tabs-title { + &:not(.is-active) { + cursor: pointer; + } + &-blue { + border-right: 2px solid #6a6a6a; + &.is-active { + background-color: $price-blue; + color: #FFF; + } + &:hover { + background-color: $price-blue; + color: $white; + } + } + &-gold { + border-right: 2px solid #6a6a6a; + &.is-active { + background-color: $price-gold; + color: #FFF; + } + &:hover { + background-color: $price-gold; + color: $white; + } + } + &-purple { + &.is-active { + background-image: linear-gradient(112deg, #5c218b, #00aef0); + color: #FFF; + } + &:hover { + background-image: linear-gradient(112deg, #5c218b, #00aef0); + color: $white; + } + } + } + .tabs-title>a:focus { + background-color: transparent; + color: $white; + } + .tabs-title>a:hover { + background-color: $price-blue; + color: $white; + } + #price-tabs-lower { + a { + padding: 0.7rem 1rem; + } + } + + .tiers-group { + margin-top: rem-calc(20); + margin-bottom: rem-calc(50); + list-style-type: none; + display: flex; + @include breakpoint($medium-large-breakpoint down) { + margin-bottom: rem-calc(10); + margin-top: rem-calc(40); + margin: auto; + } + li { + width: 120px; + height: 30px; + display: flex; + justify-content: center; + align-items: center; + @include breakpoint(small down) { + width: 75px; + } + a { + color: #6a6a6a; + font-size: rem-calc(12); + font-family: 'Roboto'; + } + &.is-active { + a { + color: $white; + } + } + } + } + .switch { + position: relative; + display: inline-block; + width: 60px; + height: 34px; + margin-left: rem-calc(30); + margin-right: rem-calc(30); + } + + /* Hide default HTML checkbox */ + .switch input { + opacity: 0; + width: 0; + height: 0; + + } + + /* The slider */ + .slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: $mystic; + -webkit-transition: .4s; + transition: .4s; + border: 2px solid #6a6a6a; + } + + .slider:before { + position: absolute; + content: ""; + height: 26px; + width: 26px; + left: 2px; + bottom: 2px; + background-color: #6a6a6a; + -webkit-transition: .4s; + transition: .4s; + } + + input.checked + .slider { + background-color: $mystic; + } + + input:focus + .slider { + box-shadow: 0 0 1px $mystic; + } + + input.checked + .slider:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); + } + + /* Rounded sliders */ + .slider.round { + border-radius: 34px; + } + + .slider.round:before { + border-radius: 50%; + } + + .card-wrapper { + margin-top: rem-calc(80); + @include breakpoint($medium-large-breakpoint down) { + margin-top: 0; + } + } + .card-outer { + flex-shrink: 3; + padding: 0 20px; + &-remove { + @include breakpoint(large up) { + padding-right: 0; + } + @include breakpoint($medium-large-breakpoint down) { + margin-top: rem-calc(90); + } + } + } + .price { + min-height: 85px; + margin-bottom: rem-calc(20); + &-blue { + color: $price-blue; + } + &-blue-alt { + color: $blue-alt; + } + &-gold { + color: $price-gold; + } + &-purple { + color: $price-purple; + } + p { + margin-bottom: 0; + @include breakpoint($medium-large-breakpoint down) { + &.sub-text:first-child { + margin-top: 4px; + } + } + &.sub-text { + font-weight: 500; + } + } + .price-per-year { + position: relative; + width: 100%; + p { + position: absolute; + width: 100%; + } + } + } + .card { + height: 482px; + box-shadow: 0 0 7px 0 rgba(0, 0, 0, 0.5); + margin-bottom: rem-calc(40); + width: 250px; + margin-left: auto; + margin-right: auto; + text-align: center; + padding-top: rem-calc(3); + padding-bottom: rem-calc(40); + @include breakpoint($medium-large-breakpoint down) { + box-shadow: none; + height: 425px; + } + + @include breakpoint($medium-large-breakpoint down) { + &.basic { + height: 400px; + } + &.plus { + height: 498px; + } + &.premium { + height: 526px; + } + } + h2 { + color: $tundora; + font-family: "Roboto Condensed"; + font-size: 18px; + font-weight: 400; + margin-top: 22px; + margin-bottom: 6px; + } + &-image-top { + margin-top: -76px; + width: 100%; + } + &-sub-header { + margin-bottom: rem-calc(15); + } + &-sub-copy { + display: flex; + align-items: center; + font-size: rem-calc(12); + margin-bottom: 8px; + text-align: left; + padding-left: rem-calc(40); + img { + padding-right: 10px; + } + } + .ghostery-free-image-container { + margin-top: -3px; + width: 250px; + height: 85px; + background-color: rgba(0, 174, 240, .25); + display: flex; + justify-content: center; + align-items: center; + @include breakpoint($medium-large-breakpoint down) { + background-color: #FFF; + } + .ghostery-free-image { + width: 55px; + height: 65px; + background-image: url('/app/images/hub/upgrade/ghostery-basic-card.svg'); + background-repeat: no-repeat; + background-position: center; + } + } + .ghostery-plus-image-container { + margin-top: -3px; + width: 250px; + height: 85px; + background-image: linear-gradient(to right, rgba(240,174,133,.25), rgba(241,216,158,.25)); + @include breakpoint($medium-large-breakpoint down) { + background-image: none; + background-color: #FFF; + } + .ghostery-plus-image { + margin: auto; + width: 225px; + height: 87px; + background-image: url('/app/images/hub/upgrade/ghostery-plus-card.svg') + } + } + .ghostery-premium-image-container { + position: relative; + } + .ghostery-premium-image { + position: absolute; + margin-top: -79px; + margin-left: 9px; + width: 233px; + height: 161px; + background-image: url('/app/images/hub/upgrade/ghostery-premium-card.svg'); + } + .ghostery-premium-image-background { + margin-top: -3px; + width: 250px; + height: 85px; + background-image: linear-gradient(to right, rgba(113,1,116,.25), rgba(90,37,142,.25) 18%, rgba(42,110,194,.25) 57%, rgba(12,156,227,.25) 86%, rgba(0,174,240,.25)); + @include breakpoint($medium-large-breakpoint down) { + background-image: none; + } + } + .button { + margin-top: 10px; + margin-bottom: rem-calc(20); + text-transform: none; + width: 177px; + height: 38px; + line-height: 22px; + &.already-protected { + @include breakpoint($medium-large-breakpoint down) { + margin-top: 45px; + } + } + } + .card-sub-header { + font-size: 14px; + } + } + + .premium-sparkle { + margin-left: 5px; + display: inline-block; + vertical-align: middle; + height: 14px; + width: 12px; + background-repeat: no-repeat; + background-image: url('/app/images/premium-sparkle.svg'); + } + .check { + display: inline-block; + width: 20px; + height: 20px; + background-repeat: no-repeat; + margin-top: 5px; + &.blue { + background-image: buildCheckIcon($price-blue); + } + &.yellow { + background-image: buildCheckIcon($price-gold); + } + &.purple { + background-image: buildCheckIcon($price-purple); + } + } + .x-icon { + display: inline-block; + height: 14px; + width: 14px; + background-image: url('/app/images/hub/upgrade/x-icon.svg') + } + .hide-for-extra-large { + .protection-description { + font-weight: bold; + margin: 20px 0 15px 0; + &.blue { + color: $price-blue; + } + &.yellow { + color: $price-gold; + } + &.purple { + color: $price-purple; + } + } + .footer-buttons { + .button { + width: 410px; + margin-top: rem-calc(25); + text-transform: none; + } + } + .protection-header { + margin-bottom: rem-calc(15); + font-size: 18px; + } + .protection-header-plus-yearly { + display: none; + &.is-active { + display: block; + } + } + } + .hide-for-extra-large { + @media only screen and (max-width: 740px) { + .card { + box-shadow: none; + margin-bottom: 0; + padding-bottom: rem-calc(15); + } + } + .price { + min-height: auto; + font-size: rem-calc(12); + } + .toggle-switch { + margin-bottom: 20px; + .toggle-switch-row { + margin-top: 22px; + } + .second-toggle { + margin-top: 0; + } + } + .table-header { + margin-top: rem-calc(20); + font-size: rem-calc(18); + } + + .checkmark { + display: none; + &.is-active { + display: inline-block; + } + &-gray { + display: none; + &.is-active { + display: inline-block; + } + } + } + tbody > tr:nth-child(2n) { + border-bottom: 1px solid #b6b6b6; + } + .table-container { + flex: 0 0 auto; + @include breakpoint($medium-large-breakpoint down) { + max-width: 100%; + } + } + } + + + .tick { font-size:18px; color:$price-blue; } + + .module-editor .learn-more { + color: $purple; + text-decoration: underline; + margin: rem-calc(15) auto 25px auto; + width: 120px; + cursor: pointer; + @include breakpoint($medium-large-breakpoint down) { + margin-top: rem-calc(5); + } + } + .module-editor .arrow { + background-image: url('/app/images/hub/upgrade/arrow-down.svg'); + width: 30px; + height: 16px; + margin-top: rem-calc(25); + margin: auto; + cursor: pointer + } + + .key-container { + height: 70px; + width: 100%; + display: flex; + align-items: center; + justify-content: flex-end; + &.mobile { + @include breakpoint($medium-large-breakpoint up) { + display: none + } + } + @include breakpoint($medium-large-breakpoint down) { + justify-content: center; + } + .midnight-note { + margin-left: 15px; + color: $price-purple-alt; + } + } + + .button { + text-transform: none; + white-space: nowrap; + font-family: 'Open Sans'; + background-color: $price-blue; + font-weight: 600; + &:hover { + background-color: $price-blue-hover; + } + } + + .button-gold { + background-color: $price-gold; + border: none; + &:hover { + background-color: $price-gold-hover; + } + } + .button-premium { + height: 38px; + width: 177px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #6c097a 50%, + #03a9ec + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + } + + .comparison-table { + background-color: $alabaster; + padding-bottom: rem-calc(100); + margin-top: rem-calc(40); + @include breakpoint($medium-large-breakpoint down) { + margin-top: 0; + padding-top: 0; + background-color: $white; + } + ul { + display:flex; + top:0px; + z-index:10; + padding-bottom:14px; + margin: 0; + @include breakpoint($medium-large-breakpoint down) { + padding-bottom: 0; + margin-bottom: rem-calc(40); + } + } + li { + list-style:none; + flex:1; + &:hover { + cursor: pointer; + } + } + li:last-child { + border-right:1px solid #b6b6b6; + @include breakpoint($medium-large-breakpoint down) { + border-right: none; + } + } + button { + width:100%; + padding: 10px; + font-size:14px; + height:60px; + color: $white; + text-transform: none; + &:hover { + cursor: pointer; + } + } + li.active button { + color: $white; + font-weight:bold; + } + @media only screen and (min-width: 832px) { + .table { + border-collapse:collapse; + width:100%; + max-width: 750px; + } + } + .mobile-table-header { + position: sticky; + top: 0; + background-color: #FFF; + } + table { + border-collapse:collapse; + width:100%; + max-width: 750px; + @include breakpoint($medium-large-breakpoint down) { + table-layout: fixed; + } + } + tbody { + @include breakpoint($medium-large-breakpoint down) { + border: 1px solid #b6b6b6; + } + } + th { + background:$alabaster; display:none; color: $white; + font-family: 'Open Sans'; + font-size: rem-calc(14); + font-weight: normal; + } + td { + font-size: rem-calc(12); + } + td, th { + height:53px; + max-width: 120px; + } + td,th { + border: 1px solid #b6b6b6; + padding: 10px; + empty-cells:show; + @include breakpoint($medium-large-breakpoint down) { + border: none; + } + } + td,th { + text-align:left; + @include breakpoint($medium-large-breakpoint down) { + text-align: center; + } + } + td+td, th+th { + text-align:center; + } + td.default { + display:table-cell; + } + tbody { + @include breakpoint($medium-large-breakpoint up) { + tr:nth-child(2n) { + background-color: #F7F7F7; + } + tr:nth-child(2n+1) { + background-color: #FFF; + } + } + @include breakpoint ($medium-large-breakpoint down) { + tr:nth-child(4n), tr:nth-child(4n+3) { + background-color: #FFF; + } + tr:nth-child(4n+2), tr:nth-child(4n+1) { + background-color: #F7F7F7; + } + } + } + tr:last-child > td { + border: none; + } + tr:last-child > td > a { + width: 104%; + height: 38px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + } + .feature-title { + margin-right: 6px; + } + .bg-blue { + background-color: $price-blue; + } + .bg-gold { + background-color: $price-gold; + } + .bg-purple-blue { + background-image: linear-gradient(120deg, #720174 0%, #5a258e 18%, #2a6ec2 57%, #0c9ce3 86%, #00aef0 100%); + } + .sep { + background:$alabaster; + font-weight:bold; + } + .txt-l { font-size:28px; font-weight:bold; } + .txt-top { position:relative; top:-9px; left:-2px; } + + .hide { + border:0; + background:none; + } + .button { + margin-bottom: 0; + @include breakpoint($medium-large-breakpoint up) { + height: 38px; + display: flex; + align-items: center; + } + @include breakpoint($medium-large-breakpoint down) { + padding-left: rem-calc(20); + padding-right: rem-calc(20); + } + } + @include breakpoint(medium up) { + ul { + display:none; + } + td,th { + display:table-cell !important; + } + tr > td:first-child { + width: 200px; + max-width: 200px; + } + } + + @include breakpoint(large up) { + td,th { + width: 200px; + max-width: 200px; + + } + td+td, th+th { + width: 166px; + max-width: 166px; + } + } + } +} diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanViewActions.js b/app/hub/Views/UpgradePlanView/UpgradePlanViewActions.js new file mode 100644 index 000000000..0db4ad954 --- /dev/null +++ b/app/hub/Views/UpgradePlanView/UpgradePlanViewActions.js @@ -0,0 +1,59 @@ +/** + * UpgradePlanView Action creators + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 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 { + TOGGLE_MONTHLY_YEARLY_PRICES, + SET_BASIC_PROTECTION, + SET_PLUS_PROTECTION, + SET_PREMIUM_PROTECTION +} from './UpgradePlanViewConstants'; + +/** + * Toggle Monthly/Yearly Prices + * @return {Object} + */ +export function toggleMonthlyYearlyPrices() { + return { + type: TOGGLE_MONTHLY_YEARLY_PRICES, + }; +} + +/** + * Set Basic protection on medium or smaller screen sizes + * @return {Object} + */ +export function setBasicProtection() { + return { + type: SET_BASIC_PROTECTION, + }; +} + +/** + * Set Plus protection on medium or smaller screen sizes + * @return {Object} + */ +export function setPlusProtection() { + return { + type: SET_PLUS_PROTECTION, + }; +} + +/** + * Set Premium protection on medium or smaller screen sizes + * @return {Object} + */ +export function setPremiumProtection() { + return { + type: SET_PREMIUM_PROTECTION, + }; +} diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanViewConstants.js b/app/hub/Views/UpgradePlanView/UpgradePlanViewConstants.js new file mode 100644 index 000000000..8635837c2 --- /dev/null +++ b/app/hub/Views/UpgradePlanView/UpgradePlanViewConstants.js @@ -0,0 +1,25 @@ +/** + * Upgrade Plan View Constants + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 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 + */ + +// Monthly/Yearly toggle +export const TOGGLE_MONTHLY_YEARLY_PRICES = 'TOGGLE_MONTHLY_YEARLY_PRICES'; + +// Free/Plus/Premium on medium or smaller screen sizes +export const SET_BASIC_PROTECTION = 'SET_BASIC_PROTECTION'; +export const SET_PLUS_PROTECTION = 'SET_PLUS_PROTECTION'; +export const SET_PREMIUM_PROTECTION = 'SET_PREMIUM_PROTECTION'; + +// Basic/Plus/Premium card to show on mobile view +export const BASIC = 'BASIC'; +export const PLUS = 'PLUS'; +export const PREMIUM = 'PREMIUM'; diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanViewReducer.js b/app/hub/Views/UpgradePlanView/UpgradePlanViewReducer.js new file mode 100644 index 000000000..69637ff8c --- /dev/null +++ b/app/hub/Views/UpgradePlanView/UpgradePlanViewReducer.js @@ -0,0 +1,59 @@ +/** + * Reducer used throughout the UpgradePlanView's flow + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 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 { + TOGGLE_MONTHLY_YEARLY_PRICES, + SET_BASIC_PROTECTION, + SET_PLUS_PROTECTION, + SET_PREMIUM_PROTECTION, + BASIC, + PLUS, + PREMIUM +} from './UpgradePlanViewConstants'; + +const initialState = { + show_yearly_prices: true, + protection_level: BASIC +}; + +function UpgradePlanViewReducer(state = initialState, action) { + switch (action.type) { + case TOGGLE_MONTHLY_YEARLY_PRICES: { + return { + ...state, + show_yearly_prices: !state.show_yearly_prices + }; + } + case SET_BASIC_PROTECTION: { + return { + ...state, + protection_level: BASIC + }; + } + case SET_PLUS_PROTECTION: { + return { + ...state, + protection_level: PLUS + }; + } + case SET_PREMIUM_PROTECTION: { + return { + ...state, + protection_level: PREMIUM + }; + } + default: return state; + } +} + +export default UpgradePlanViewReducer; diff --git a/app/hub/Views/UpgradePlanView/index.js b/app/hub/Views/UpgradePlanView/index.js new file mode 100644 index 000000000..3ebc11681 --- /dev/null +++ b/app/hub/Views/UpgradePlanView/index.js @@ -0,0 +1,42 @@ +/** + * Point of entry index.js file for UpgradePlanView + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2020 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 { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import { withRouter } from 'react-router-dom'; + +import UpgradePlanView from './UpgradePlanView'; +import UpgradePlanViewReducer from './UpgradePlanViewReducer'; +import * as UpgradePlanViewActions from './UpgradePlanViewActions'; + +/** + * Map redux store state properties to the component's own properties. + * @param {Object} state entire Redux store's state + * @return {function} this function returns a plain object, which will be merged into the component's props + * @memberof HubContainers + */ +const mapStateToProps = state => ({ ...state.upgrade, ...state.account }); + +/** + * Bind the component's action creators using Redux's bindActionCreators. + * @param {function} dispatch redux store method which dispatches actions + * @return {function} to be used as an argument in redux connect call + * @memberof SetupContainers + */ +const mapDispatchToProps = dispatch => ({ + actions: bindActionCreators({ ...UpgradePlanViewActions }, dispatch), +}); + +export const reducer = UpgradePlanViewReducer; + +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(UpgradePlanView)); diff --git a/app/hub/createStore.js b/app/hub/createStore.js index bfa1bb2dc..8718724ef 100644 --- a/app/hub/createStore.js +++ b/app/hub/createStore.js @@ -25,6 +25,7 @@ import { reducer as app } from './Views/AppView'; import { reducer as home } from './Views/HomeView'; import { reducer as setup } from './Views/SetupView'; import { reducer as tutorial } from './Views/TutorialView'; +import { reducer as upgrade } from './Views/UpgradePlanView'; import account from '../Account/AccountReducer'; import settings from '../panel/reducers/settings'; @@ -35,6 +36,7 @@ const reducer = combineReducers({ tutorial, account, settings, + upgrade }); /** diff --git a/app/hub/index.jsx b/app/hub/index.jsx index 020cde46e..6e8f215ab 100644 --- a/app/hub/index.jsx +++ b/app/hub/index.jsx @@ -30,6 +30,7 @@ import ProductsView from './Views/ProductsView'; import CreateAccountView from './Views/CreateAccountView'; import ForgotPasswordView from '../shared-components/ForgotPassword/ForgotPasswordContainer'; import LogInView from './Views/LogInView'; +import UpgradePlanView from './Views/UpgradePlanView'; const store = createStore(); @@ -39,7 +40,8 @@ const store = createStore(); */ const Hub = () => ( - + + diff --git a/app/images/hub/upgrade/arrow-down.svg b/app/images/hub/upgrade/arrow-down.svg new file mode 100644 index 000000000..2fb5d83b4 --- /dev/null +++ b/app/images/hub/upgrade/arrow-down.svg @@ -0,0 +1 @@ + diff --git a/app/images/hub/upgrade/check.svg b/app/images/hub/upgrade/check.svg new file mode 100644 index 000000000..b1b7defab --- /dev/null +++ b/app/images/hub/upgrade/check.svg @@ -0,0 +1 @@ + diff --git a/app/images/hub/upgrade/ghostery-basic-card.svg b/app/images/hub/upgrade/ghostery-basic-card.svg new file mode 100644 index 000000000..13f10f619 --- /dev/null +++ b/app/images/hub/upgrade/ghostery-basic-card.svg @@ -0,0 +1 @@ + diff --git a/app/images/hub/upgrade/ghostery-plus-card.svg b/app/images/hub/upgrade/ghostery-plus-card.svg new file mode 100644 index 000000000..575180465 --- /dev/null +++ b/app/images/hub/upgrade/ghostery-plus-card.svg @@ -0,0 +1 @@ + diff --git a/app/images/hub/upgrade/ghostery-premium-card.svg b/app/images/hub/upgrade/ghostery-premium-card.svg new file mode 100644 index 000000000..03fb33f37 --- /dev/null +++ b/app/images/hub/upgrade/ghostery-premium-card.svg @@ -0,0 +1 @@ + diff --git a/app/images/hub/upgrade/x-icon.svg b/app/images/hub/upgrade/x-icon.svg new file mode 100644 index 000000000..cde0bd209 --- /dev/null +++ b/app/images/hub/upgrade/x-icon.svg @@ -0,0 +1 @@ + diff --git a/app/images/premium-sparkle.svg b/app/images/premium-sparkle.svg new file mode 100644 index 000000000..5a9551c96 --- /dev/null +++ b/app/images/premium-sparkle.svg @@ -0,0 +1 @@ + diff --git a/app/panel/components/HeaderMenu.jsx b/app/panel/components/HeaderMenu.jsx index 620c810a3..57ddbcbc8 100644 --- a/app/panel/components/HeaderMenu.jsx +++ b/app/panel/components/HeaderMenu.jsx @@ -268,7 +268,7 @@ class HeaderMenu extends React.Component { )} - {hasPremiumAccess ? t('panel_detail_premium_title') : t('ghostery_plus')} + {hasPremiumAccess ? 'Ghostery Premium' : 'Ghostery Plus'}
diff --git a/app/panel/components/Help.jsx b/app/panel/components/Help.jsx index 777f7d1e5..ef5f442b9 100644 --- a/app/panel/components/Help.jsx +++ b/app/panel/components/Help.jsx @@ -12,37 +12,33 @@ */ import React from 'react'; -import { openSupportPage } from '../utils/msg'; +import { openSupportPage, openHubPage } from '../utils/msg'; import PanelToTabLink from './BuildingBlocks/PanelToTabLink'; /** * Render Help view that user can open from the header drop-down menu */ -const Help = () => { - const hubUrl = chrome.runtime.getURL('./app/templates/hub.html'); - - return ( -
-
-
-

{ t('panel_help_panel_header') }

-
- {t('panel_help_setup')} -
-
-

{ t('panel_help_questions_header') }

- {t('panel_help_faq')} - {t('panel_help_feedback')} - { t('support') } -
-
-

{ t('panel_help_contact_header') }

- info@ghostery.com -
+const Help = () => ( +
+
+
+

{t('panel_help_panel_header')}

+ +
+

{t('panel_help_questions_header')}

+ {t('panel_help_faq')} + {t('panel_help_feedback')} + {t('support')} +
+
+

{t('panel_help_contact_header')}

+ info@ghostery.com
- ); -}; +
+); export default Help; diff --git a/app/panel/components/Subscription/SubscriptionInfo.jsx b/app/panel/components/Subscription/SubscriptionInfo.jsx index 46d8610b9..7c4e02f29 100644 --- a/app/panel/components/Subscription/SubscriptionInfo.jsx +++ b/app/panel/components/Subscription/SubscriptionInfo.jsx @@ -46,10 +46,10 @@ const SubscriptionInfo = ({ subscriptionData }) => {
{productName === 'Ghostery Premium' && ( -

{ t('panel_detail_premium_title') }

+

Ghostery Premium

)} {productName === 'Ghostery Plus' && ( -

{ t('ghostery_plus') }

+

Ghostery Plus

)} {loading ? (
diff --git a/app/panel/utils/msg.js b/app/panel/utils/msg.js index 78deb224a..c966af4f1 100644 --- a/app/panel/utils/msg.js +++ b/app/panel/utils/msg.js @@ -139,3 +139,14 @@ export function openSupportPage(e) { sendMessage('account.openSupportPage'); window.close(); } + +/** + * Send a message to open the hub + * This should be used for messages that don't require a callback. + * @memberOf PanelUtils + */ +export function openHubPage(e) { + e.preventDefault(); + sendMessage('openHubPage'); + window.close(); +} diff --git a/app/scss/hub.scss b/app/scss/hub.scss index 2c220f43d..60394aad7 100644 --- a/app/scss/hub.scss +++ b/app/scss/hub.scss @@ -73,6 +73,7 @@ html, body, #root { @import '../hub/Views/SignedInView/SignedInView.scss'; @import '../hub/Views/LogInView/LogInView.scss'; @import '../hub/Views/CreateAccountView/CreateAccountView.scss'; +@import '../hub/Views/UpgradePlanView/UpgradePlanView.scss'; // Imports from ../shared-components directory @import '../shared-components/ExitButton/ExitButton.scss'; diff --git a/app/scss/partials/_hub_svgs.scss b/app/scss/partials/_hub_svgs.scss index 5d90c76ac..63ad5deb3 100644 --- a/app/scss/partials/_hub_svgs.scss +++ b/app/scss/partials/_hub_svgs.scss @@ -44,6 +44,9 @@ @function buildIconHome($primary-color) { @return url('data:image/svg+xml;charset%3dUS-ASCII,%3Csvg%20width%3D%2223%22%20height%3D%2223%22%20xmlns%3D%22http://www.w3.org/2000/svg%22%3E%3Cg%20fill-rule%3D%22nonzero%22%20fill%3D%22none%22%3E%3Cpath%20d%3D%22M1%2021.914V8.55L11.5%201%2022%208.55v13.364h-7v-2.986c0-2.893-1.167-4.339-3.5-4.339S8%2016.035%208%2018.928v2.986H1z%22%20stroke%3D%22#{_url-friendly-color($primary-color)}%22%20stroke-width%3D%221%22%20stroke-linejoin%3D%22round%22/%3E%3Cpath%20d%3D%22M-1-1h25v25H-1z%22/%3E%3C/g%3E%3C/svg%3E'); } +@function buildIconShield($primary-color) { + @return url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http://www.w3.org/2000/svg%22%20width%3D%2229%22%20height%3D%2231%22%3E%3Cg%20fill%3D%22#{_url-friendly-color($primary-color)}%22%3E%3Cpath%20d%3D%22M14.809%2015.701c-2.186-.002-3.958-1.922-3.96-4.29%200-.138-.103-.249-.23-.249-.126%200-.23.112-.23.25-.002%202.367-1.773%204.286-3.96%204.29-.127%200-.229.111-.229.248s.103.25.23.25c2.186.001%203.957%201.92%203.96%204.289%200%20.138.103.249.23.249.126%200%20.23-.111.23-.25.001-2.367%201.773-4.286%203.959-4.289.127%200%20.23-.112.23-.249s-.103-.249-.23-.249zm8.179-2.87c-1.458%200-2.639-1.33-2.64-2.97%200-.095-.069-.172-.153-.172-.085%200-.154.078-.154.173-.001%201.64-1.182%202.968-2.64%202.97-.084%200-.153.077-.153.172%200%20.096.069.172.154.172%201.457.002%202.638%201.33%202.64%202.97%200%20.096.068.172.153.172.084%200%20.153-.077.153-.172.001-1.64%201.182-2.968%202.64-2.97.084%200%20.153-.077.153-.172%200-.096-.069-.172-.153-.172zm-5.835-5.815a1.653%201.653%200%2001-1.65-1.65.096.096%200%2000-.192%200%201.653%201.653%200%2001-1.65%201.65.095.095%200%2000-.095.095c0%20.053.042.096.095.096.911.001%201.649.74%201.65%201.65a.096.096%200%2000.192%200%201.652%201.652%200%20011.65-1.65.095.095%200%2000.095-.096.095.095%200%2000-.095-.095z%22/%3E%3Cpath%20d%3D%22M14.462%2030.569l-.549-.316a1.44%201.44%200%2001-.11-.07l-.009-.005C5.844%2024.992%201.096%2017.04.068%207.187a23.608%2023.608%200%2001-.063-.767A2.059%202.059%200%20011.47%204.294C2.58%203.96%207.77%202.061%2012.256.395a6.34%206.34%200%20014.412.001c4.487%201.666%209.677%203.564%2010.785%203.898a2.063%202.063%200%20011.466%202.13c-.02.3-.044.585-.062.763-1.029%209.855-5.776%2017.805-13.73%2022.992l-.005.004c-.023.015-.062.042-.11.07l-.55.316zM2.207%206.37c.017.237.036.455.05.587.933%208.948%205.15%2016.202%2012.205%2021.02%207.058-4.82%2011.272-12.073%2012.206-21.02.013-.132.032-.35.049-.587-1.658-.52-8.668-3.114-10.815-3.91a4.142%204.142%200%2000-2.88-.002C10.876%203.256%203.87%205.85%202.207%206.37z%22/%3E%3C/g%3E%3C/svg%3E'); +} @function buildIconSetup($primary-color) { @return url('data:image/svg+xml;charset%3dUS-ASCII,%3Csvg%20width%3D%2223%22%20height%3D%2228%22%20xmlns%3D%22http://www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22#{_url-friendly-color($primary-color)}%22%20stroke%3D%22#{_url-friendly-color($primary-color)}%22%20stroke-width%3D%220.1%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M11.464%204.563c.612.004%201.135-.523%201.147-1.155.012-.638-.511-1.188-1.133-1.192-.613-.005-1.136.523-1.147%201.155-.012.637.512%201.187%201.133%201.192zm-3.415%200H3.596c-.777%200-1.241.485-1.241%201.291v18.56c0%20.78.478%201.27%201.24%201.27h15.746c.776%200%201.247-.486%201.247-1.286V5.838c0-.783-.475-1.275-1.235-1.275H14.883v1.156H8.048V4.563zM9.184%203.39c.042-1.4%201.089-2.278%202.145-2.35%201.086-.072%202.348.726%202.44%202.35h.242c1.807%200%203.615-.002%205.423%200%201.293%200%202.292%201.029%202.292%202.362V24.5c0%201.332-.999%202.356-2.298%202.356H3.5c-1.282%200-2.285-1.032-2.285-2.347V5.786c0-1.385.981-2.394%202.329-2.395%201.793-.002%203.584%200%205.377%200h.262z%22/%3E%3Cpath%20d%3D%22M5.755%2011.343h11.412v-.97H5.755zM5.755%2013.605h11.412v-.97H5.755zM5.755%2016.19h11.412v-.969H5.755zM5.755%2018.453h11.412v-.97H5.755z%22/%3E%3C/g%3E%3C/svg%3E'); } @@ -80,6 +83,11 @@ @return url('data:image/svg+xml;charset%3dUS-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2249%22%20height%3D%2250%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20fill%3D%22#{_url-friendly-color($primary-color)}%22%20d%3D%22M5%207.108L26.488%200%2049%207.108c-.964%208.337-2.328%2014.769-4.093%2019.293-2.648%206.787-3.803%207.353-8.186%2012.185-2.922%203.222-6.333%205.93-10.233%208.124-.682%201.354-4.093-2.031-10.232-10.154C10.116%2028.432%206.364%2018.616%205%207.108z%22%2F%3E%3Cg%20fill%3D%22#{_url-friendly-color($secondary-color)}%22%20fill-rule%3D%22nonzero%22%3E%3Cpath%20d%3D%22M23.885%2025.324c-3.526-.003-6.383-3.099-6.386-6.918%200-.224-.167-.402-.371-.402s-.37.18-.37.402c-.004%203.819-2.861%206.914-6.387%206.918-.206%200-.371.181-.371.402%200%20.221.166.402.37.402%203.527.003%206.384%203.099%206.387%206.918%200%20.223.167.402.371.402s.37-.18.37-.402c.004-3.819%202.861-6.914%206.387-6.918.206%200%20.371-.181.371-.402%200-.221-.166-.402-.37-.402zm13.192-4.628c-2.351-.002-4.256-2.146-4.258-4.79%200-.154-.11-.278-.247-.278s-.247.125-.247.278c-.002%202.644-1.907%204.788-4.258%204.79-.137%200-.247.125-.247.278%200%20.154.11.278.247.278%202.351.002%204.256%202.146%204.258%204.79%200%20.154.11.278.247.278s.247-.125.247-.278c.002-2.644%201.907-4.788%204.258-4.79.137%200%20.247-.125.247-.278%200-.154-.11-.278-.247-.278zm-9.411-9.38a2.665%202.665%200%2001-2.662-2.662.154.154%200%2000-.308%200%202.665%202.665%200%2001-2.662%202.662.154.154%200%20000%20.308%202.665%202.665%200%20012.662%202.662.154.154%200%2000.308%200%202.665%202.665%200%20012.662-2.662.154.154%200%20000-.308z%22%2F%3E%3Cpath%20d%3D%22M23.326%2049.305l-.885-.51c-.08-.045-.14-.087-.178-.112l-.014-.01C9.425%2040.31%201.767%2027.486.109%2011.591c-.03-.286-.067-.748-.1-1.235a3.322%203.322%200%20012.362-3.429c1.79-.539%2010.16-3.601%2017.397-6.288C20.911.215%2022.108%200%2023.326%200s2.416.215%203.558.64c7.237%202.686%2015.608%205.748%2017.394%206.287a3.322%203.322%200%20012.366%203.435c-.033.482-.071.943-.1%201.23-1.66%2015.895-9.317%2028.718-22.146%2037.085l-.008.005c-.038.025-.1.068-.178.113l-.886.51zM3.56%2010.275c.028.383.057.735.08.948%201.505%2014.432%208.305%2026.132%2019.686%2033.902%2011.384-7.774%2018.181-19.472%2019.687-33.902.022-.213.051-.566.079-.947-2.674-.838-13.98-5.023-17.443-6.308a6.653%206.653%200%2000-4.646-.002C17.543%205.252%206.242%209.434%203.56%2010.275z%22%2F%3E%3C%2Fg%3E%3C%2Fg%3E%3C%2Fsvg%3E'); } +// Used in Upgrade Plan View +@function buildCheckIcon($primary-color) { + @return url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http://www.w3.org/2000/svg%22%20width%3D%2214%22%20height%3D%2213%22%3E%3Cpath%20fill%3D%22#{_url-friendly-color($primary-color)}%22%20d%3D%22M5.47%2012.45L0%206.98l1.54-1.54%203.71%203.71%207-9.15%201.73%201.32z%22/%3E%3C/svg%3E%0A'); +} + // Used in Setup Anti Suite View @function buildAntiTrackingIcon($primary-color) { // Also used in Tutorial Anti Suite View @return url('data:image/svg+xml;charset%3dUS-ASCII,%3Csvg%20width%3D%2223%22%20height%3D%2230%22%20xmlns%3D%22http://www.w3.org/2000/svg%22%3E%3Cg%20transform%3D%22translate(2.08)%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M9.804%209.03a.692.692%200%200%200-.408%200L.613%2011.36a.787.787%200%200%200-.613.777c.041%206.254%203.39%2012.057%209.15%2015.94a.825.825%200%200%200%20.45.123.825.825%200%200%200%20.45-.123c5.76-3.883%209.109-9.686%209.15-15.94a.787.787%200%200%200-.613-.777L9.804%209.03z%22%20stroke%3D%22#{_url-friendly-color($primary-color)}%22%20stroke-width%3D%222.3%22/%3E%3C/g%3E%3C/svg%3E'); diff --git a/src/background.js b/src/background.js index a4ea5e6d0..e0d614bf1 100644 --- a/src/background.js +++ b/src/background.js @@ -927,7 +927,7 @@ function onMessageHandler(request, sender, callback) { const user = { user: { ...foundUser } }; if (foundUser) { user.user.plusAccess = account.hasScopesUnverified(['subscriptions:plus']) - || account.hasScopesUnverified(['subscriptions:premium']); + || account.hasScopesUnverified(['subscriptions:premium']); user.user.premiumAccess = account.hasScopesUnverified(['subscriptions:premium']); } callback(user); @@ -1025,6 +1025,12 @@ function onMessageHandler(request, sender, callback) { }); return true; } + if (name === 'openHubPage') { + const hubUrl = chrome.runtime.getURL('./app/templates/hub.html'); + metrics.ping('intro_hub_click'); + utils.openNewTab({ url: hubUrl, become_active: true }); + return false; + } if (name === 'promoModals.sawPremiumPromo') { promoModals.recordPremiumPromoSighting(); return false; diff --git a/src/classes/Metrics.js b/src/classes/Metrics.js index 6de30756c..fb34f64f0 100644 --- a/src/classes/Metrics.js +++ b/src/classes/Metrics.js @@ -254,6 +254,12 @@ class Metrics { this._sendReq(type, ['all']); break; + // Onboarding Pings - Ghostery 8.5.2+ + case 'intro_hub_click': + case 'intro_hub_home_upgrade': + this._sendReq(type, ['all']); + break; + // Uncaught Pings default: log(`metrics ping() error: ping name ${type} not found`);