diff --git a/.tx/config b/.tx/config index 220c7d6b5..49ca0defa 100644 --- a/.tx/config +++ b/.tx/config @@ -1,9 +1,10 @@ -[main] -host = https://www.transifex.com - -[ghostery-browser-extension.develop-en-messages-json] -file_filter = _locales//messages.json -minimum_perc = 0 -source_file = _locales/en/messages.json -source_lang = en -type = CHROME +[main] +host = https://www.transifex.com + +[ghostery-browser-extension.develop-en-messages-json] +file_filter = _locales//messages.json +minimum_perc = 0 +source_file = _locales/en/messages.json +source_lang = en +type = CHROME + diff --git a/CODEOWNERS b/CODEOWNERS index 0ec6fd468..dff693b03 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,12 +8,13 @@ # CMP / Metrics / WebRequest /src/classes/ABTest.js @wlycdgr /src/classes/CMP.js @christophertino -/src/classes/EventHandlers.js @christophertino -/src/classes/Metrics.js @wlycdgr -/src/classes/PolicySmartBlock.js @christophertino /src/classes/Conf.js @christophertino /src/classes/ConfData.js @christophertino +/src/classes/Debuggers.js @wlycdgr +/src/classes/EventHandlers.js @christophertino +/src/classes/Metrics.js @wlycdgr /src/classes/PanelData.js @wlycdgr +/src/classes/PolicySmartBlock.js @christophertino # Background /src/background.js @christophertino @@ -21,6 +22,9 @@ # The Ghostery Hub /app/hub/ @benstrumeyer +# Dawn Hub +/app/dawn-hub @wlycdgr + # Shared Components /app/shared-components @wlycdgr diff --git a/_locales/en/messages.json b/_locales/en/messages.json index ef5448340..765174e11 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -1366,6 +1366,9 @@ "hub_upgrade_additional_protection": { "message": "Additional Protection" }, + "hub_upgrade_maximum_protection": { + "message": "Maximum Protection" + }, "hub_upgrade_browser_tracker_blocking": { "message": "Browser Tracker Blocking" }, @@ -1722,6 +1725,9 @@ "hub_create_account_label_password_invalid_length": { "message": "Use between 8 and 50 characters." }, + "hub_create_account_confirm_password_do_not_match": { + "message": "Your passwords do not match." + }, "hub_create_account_already_have_account": { "message": "Already have a Ghostery Account?" }, @@ -1734,6 +1740,225 @@ "hub_create_account_toast_error": { "message": "That email address is already in use. Please choose another." }, + "ghostery_dawn_onboarding_toast_fix_errors": { + "message": "Please fix errors before proceeding." + }, + "ghostery_dawn_onboarding_install_heading": { + "message": "Ghostery Dawn now installing..." + }, + "ghostery_dawn_onboarding_page_title": { + "message": "Ghostery Dawn Hub - Welcome" + }, + "ghostery_dawn_onboarding_header_title_login": { + "message": "Ghostery Dawn Hub - Login" + }, + "ghostery_dawn_onboarding_header_title_block_settings": { + "message": "Ghostery Dawn Hub - Block Settings" + }, + "ghostery_dawn_onboarding_header_title_search_choice": { + "message": "Ghostery Dawn Hub - Search Choice" + }, + "ghostery_dawn_onboarding_header_title_plan_choices": { + "message": "Ghostery Dawn Hub - Plan Choices" + }, + "ghostery_dawn_onboarding_privacy": { + "message": "Privacy" + }, + "ghostery_dawn_onboarding_search": { + "message": "Search" + }, + "ghostery_dawn_onboarding_plan": { + "message": "Plan" + }, + "ghostery_dawn_onboarding_welcome": { + "message": "Welcome to Ghostery Dawn!" + }, + "ghostery_dawn_onboarding_lets_begin": { + "message": "We've centralized online privacy by integrating our signature as well as novel technologies." + }, + "ghostery_dawn_onboarding_welcome_message": { + "message": "Our browser protects your data and blocks bloatware so that you can surf privately and over 2x faster than with Chrome." + }, + "ghostery_dawn_onboarding_lets_do_this": { + "message": "Set up My Browser" + }, + "ghostery_dawn_onboarding_your_privacy_plan": { + "message": "Your Privacy Plan" + }, + "ghostery_dawn_onboarding_based_on_your_privacy_preferences": { + "message": "Based on your privacy preferences" + }, + "ghostery_dawn_onboarding_ad_free_with_ghostery_plus_subscription": { + "message": "Ad-free with Ghostery Plus subscription" + }, + "ghostery_dawn_onboarding_ad_free_promo": { + "message": "40% off for the first 12 months" + }, + "ghostery_dawn_onboarding_ad_free_promo_description": { + "message": "Get to what you want faster. A Plus subscription gives Ghostery the support we need to provide YOU with an ad-free experience." + }, + "ghostery_dawn_onboarding_or": { + "message": "OR" + }, + "ghostery_dawn_onboarding_keep": { + "message": "Keep" + }, + "ghostery_dawn_onboarding_upgrade": { + "message": "Upgrade" + }, + "ghostery_dawn_onboarding_start_trial": { + "message": "Start Trial" + }, + "ghostery_dawn_onboarding_see_all_plans": { + "message": "See all plans" + }, + "ghostery_dawn_onboarding_private_search": { + "message": "Private search" + }, + "ghostery_dawn_onboarding_tracker_protection": { + "message": "Tracker protection" + }, + "ghostery_dawn_onboarding_speedy_page_loads": { + "message": "Speedy page loads" + }, + "ghostery_dawn_onboarding_intelligence_technology": { + "message": "Intelligence technology" + }, + "ghostery_dawn_onboarding_ad_free": { + "message": "Ad-free" + }, + "ghostery_dawn_onboarding_supports_ghosterys_mission": { + "message": "Supports Ghostery's mission" + }, + "ghostery_dawn_onboarding_unlimited_bandwidth": { + "message": "Unlimited Bandwidth" + }, + "ghostery_dawn_onboarding_already_premium_subscriber": { + "message": "You are already a Premium Subscriber" + }, + "ghostery_dawn_onboarding_already_plus_subscriber": { + "message": "You are already a Plus Subscriber" + }, + "ghostery_dawn_onboarding_keep_your_current_plan_or_upgrade": { + "message": "Keep your current plan or upgrade" + }, + "ghostery_dawn_onboarding_choose_an_option": { + "message": "Choose an option" + }, + "ghostery_dawn_onboarding_create_a_ghostery_account": { + "message": "Create a Ghostery Account" + }, + "ghostery_dawn_onboarding_sync_settings": { + "message": "Allows you to sync your privacy settings across devices." + }, + "ghostery_dawn_onboarding_already_have_account": { + "message": "I already have an account." + }, + "ghostery_dawn_onboarding_skip": { + "message": "Skip" + }, + "ghostery_dawn_onboarding_back": { + "message": "Back" + }, + "ghostery_dawn_onboarding_we_take_your_privacy_very_seriously": { + "message": "We take your privacy very seriously. Learn more" + }, + "ghostery_dawn_onboarding_private_by_design": { + "message": "Private by design" + }, + "ghostery_dawn_onboarding_private_by_design_description": { + "message": "Privacy is incredibly important to us at Ghostery. That’s why we enforce a strict policy when it comes to your data. When you create an account at Ghostery, the only data we collect is your name and email. This information allows us to offer you a user experience wherein you can sync your settings across devices and hear from us about important updates. Under no circumstances do we share or sell this data with any kind of third party." + }, + "ghostery_dawn_onboarding_can_i_remove_my_account": { + "message": "Can I remove my account?" + }, + "ghostery_dawn_onboarding_can_i_remove_my_account_description": { + "message": "We hope you enjoy using Ghostery, but if you do decide to leave we will remove all of your personal information within 90 days from the date of request." + }, + "ghostery_dawn_onboarding_visit_our_privacy_policy": { + "message": "Visit our Privacy Policy for more information" + }, + "ghostery_dawn_onboarding_create_an_account": { + "message": "Create an account." + }, + "ghostery_dawn_onboarding_you_are_signed_in_as": { + "message": "You are signed in as" + }, + "ghostery_dawn_onboarding_youve_successfully_set_up_your_browser": { + "message": "You've successfully set up your browser!" + }, + "ghostery_dawn_onboarding_surf_with_ease": { + "message": "Now you can surf with the greatest of ease." + }, + "ghostery_dawn_onboarding_start_browsing": { + "message": "Start Browsing" + }, + "ghostery_dawn_onboarding_which_privacy_plan": { + "message": "Which privacy plan is right for you?" + }, + "ghostery_dawn_onboarding_tell_us_your_preferences": { + "message": "Tell us your preferences, we'll offer you our recommendation." + }, + "ghostery_dawn_onboarding_recommended_choices": { + "message": "Recommended choices" + }, + "ghostery_dawn_onboarding_question_block_ads": { + "message": "Do you want to block ads?" + }, + "ghostery_dawn_onboarding_question_kinds_of_trackers": { + "message": "What kinds of trackers do you want to block?" + }, + "ghostery_dawn_onboarding_kinds_of_trackers_all": { + "message": "All trackers" + }, + "ghostery_dawn_onboarding_kinds_of_trackers_none": { + "message": "No trackers" + }, + "ghostery_dawn_onboarding_kinds_of_trackers_ad_and_analytics": { + "message": "All ad and analytics trackers" + }, + "ghostery_dawn_onboarding_question_anti_tracking": { + "message": "Do you want to turn on anti-tracking?" + }, + "ghostery_dawn_onboarding_question_smart_browsing": { + "message": "Do you want to turn on smart-browsing?" + }, + "ghostery_dawn_onboarding_info_blocking_all": { + "message": "Blocking \"all trackers\" may cause some websites to break." + }, + "ghostery_dawn_onboarding_info_anti_tracking": { + "message": "Anti-tracking anonymizes uniquely identifiable data that trackers try to collect." + }, + "ghostery_dawn_onboarding_info_smart_browsing": { + "message": "Smart-browsing adjusts your blocking settings to decrease page breakage and accelerate page loads." + }, + "ghostery_dawn_onboarding_ad_free_private_search": { + "message": "Ad-free private search" + }, + "ghostery_dawn_onboarding_recommended" : { + "message": "(Recommended)" + }, + "ghostery_dawn_onboarding_ad_supported_private_search": { + "message": "Ad-supported private search" + }, + "ghostery_dawn_onboarding_type_in_search_domain": { + "message": "Type in search domain" + }, + "ghostery_dawn_onboarding_go_back": { + "message": "Go Back" + }, + "ghostery_dawn_onboarding_confirm": { + "message": "Confirm" + }, + "ghostery_dawn_onboarding_other": { + "message": "Other" + }, + "ghostery_dawn_onboarding_toast_success": { + "message": "Success: " + }, + "ghostery_dawn_onboarding_toast_alert": { + "message": "Error: " + }, "enable_when_paused": { "message": "To use this function, Resume Ghostery." }, @@ -2033,6 +2258,9 @@ "next": { "message": "Next" }, + "next_or_start_trial": { + "message": "Next or Start Trial" + }, "previous": { "message": "Previous" }, @@ -2054,6 +2282,9 @@ "password_colon": { "message": "Password:" }, + "confirm_password_colon": { + "message": "Confirm Password:" + }, "please_enter_a_valid_email": { "message": "Please enter a valid email." }, @@ -2177,5 +2408,41 @@ }, "too_many_failed_logins_text": { "message": "Too many failed logins. Try again in one hour." + }, + "choose_your_default_search": { + "message": "Choose your default search" + }, + "pick_a_default_search_engine": { + "message": "Choose a default search engine for all your searches." + }, + "ghostery_dawn_hub_blocking_settings_view_toast_error_message": { + "message": "Please answer all questions" + }, + "ghostery_dawn_onboarding_startpage_warning": { + "message": "Just so you know: Startpage's search engine does not collect your data. In order to keep privacy protection free though, it will serve you untargeted, private ads." + }, + "ghostery_dawn_onboarding_bing_warning": { + "message": "Just so you know: Bing's search engine will log your data and use it to serve you targeted ads." + }, + "ghostery_dawn_onboarding_yahoo_warning": { + "message": "Just so you know: Yahoo's search engine will log your data and use it to serve you targeted ads." + }, + "ghostery_dawn_onboarding_glow_benefit": { + "message": "With a Plus subscription, you can access the world’s first and only ad-free private search! We use a special cookie-less login to keep your search absolutely private." + }, + "ghostery_dawn_onboarding_choose_alternate_search": { + "message": "Choose alternate search" + }, + "ghostery_dawn_onboarding_select_option": { + "message": "Select option" + }, + "ghostery_dawn_onboarding_you_have_selected_an_alternate_search_engine": { + "message": "You have selected an alternate search engine:" + }, + "ghostery_dawn_onboarding_create_account_for_trial": { + "message": "To start your Ghostery Search free trial, you must create or log into an account. This allows you to sync your privacy settings across devices." + }, + "ghostery_dawn_onboarding_back_to_search_selection": { + "message": "Back to search selection" } } diff --git a/app/Account/AccountActions.js b/app/Account/AccountActions.js index 4114a025b..9211d5c8b 100644 --- a/app/Account/AccountActions.js +++ b/app/Account/AccountActions.js @@ -27,7 +27,8 @@ import { GET_USER_SETTINGS_SUCCESS, GET_USER_SETTINGS_FAIL, GET_USER_SUBSCRIPTION_DATA_FAIL, - GET_USER_SUBSCRIPTION_DATA_SUCCESS + GET_USER_SUBSCRIPTION_DATA_SUCCESS, + SUBSCRIBE_TO_EMAIL_LIST } from './AccountConstants'; import { SET_TOAST } from '../hub/Views/AppView/AppViewConstants'; import { CLEAR_THEME } from '../panel/constants/constants'; @@ -201,3 +202,10 @@ export const resetPassword = email => dispatch => ( }); }) ); + +export const subscribeToEmailList = name => dispatch => ( + dispatch({ + type: SUBSCRIBE_TO_EMAIL_LIST, + payload: { name }, + }) +); diff --git a/app/Account/AccountConstants.js b/app/Account/AccountConstants.js index 64eb24a63..e3a4f2bae 100644 --- a/app/Account/AccountConstants.js +++ b/app/Account/AccountConstants.js @@ -36,3 +36,6 @@ export const GET_USER_SETTINGS_FAIL = 'GET_USER_SETTINGS_FAIL'; // Update Subscription Data export const GET_USER_SUBSCRIPTION_DATA_FAIL = 'GET_USER_SUBSCRIPTION_DATA_FAIL'; export const GET_USER_SUBSCRIPTION_DATA_SUCCESS = 'GET_USER_SUBSCRIPTION_DATA_SUCCESS'; + +// Opt into/out of updates and promotions sendgrid emails +export const SUBSCRIBE_TO_EMAIL_LIST = 'SUBSCRIBE_TO_EMAIL_LIST'; diff --git a/app/Account/AccountReducer.js b/app/Account/AccountReducer.js index ebc31da25..e8b63cc0b 100644 --- a/app/Account/AccountReducer.js +++ b/app/Account/AccountReducer.js @@ -20,7 +20,8 @@ import { GET_USER_SUBSCRIPTION_DATA_FAIL, GET_USER_SUBSCRIPTION_DATA_SUCCESS, RESET_PASSWORD_SUCCESS, - RESET_PASSWORD_FAIL + RESET_PASSWORD_FAIL, + SUBSCRIBE_TO_EMAIL_LIST } from './AccountConstants'; import { UPDATE_PANEL_DATA } from '../panel/constants/constants'; @@ -118,6 +119,15 @@ export default (state = initialState, action) => { resetPasswordError: true }; } + case SUBSCRIBE_TO_EMAIL_LIST: { + const { name } = action.payload; + let emailPreferences; + if (name === 'global') { + emailPreferences = { ...state.user.emailPreferences, ...{ global: true } }; + } + const user = { ...state.user, ...{ emailPreferences } }; + return { ...state, ...{ user } }; + } default: return state; } diff --git a/app/dawn-hub/Views/AppView/AppView.jsx b/app/dawn-hub/Views/AppView/AppView.jsx new file mode 100644 index 000000000..a24665602 --- /dev/null +++ b/app/dawn-hub/Views/AppView/AppView.jsx @@ -0,0 +1,66 @@ +/** + * Dawn Hub App View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { ToastMessage } from '../../../shared-components'; + +/** + * A functional React component that lies at the root of the component hierarchy for the Dawn Hub + * @extends Component + * @memberof DawnHubViews + */ +const AppView = (props) => { + const { toast, children } = props; + const { toastMessage, toastClass } = toast; + + /** + * Handle clicking to exit the Toast Message. + */ + const exitToast = () => { + const { actions } = props; + actions.setToast({ + toastMessage: '', + toastClass: '', + }); + }; + + return ( +
+
+ {} + {children} +
+
+ ); +}; + +AppView.propTypes = { + actions: PropTypes.shape({ + setToast: PropTypes.func.isRequired, + }).isRequired, + toast: PropTypes.shape({ + toastMessage: PropTypes.string, + toastClass: PropTypes.string, + }), +}; + +// Default props used in the App +AppView.defaultProps = { + toast: { + toastMessage: '', + toastClass: '', + }, +}; + +export default AppView; diff --git a/app/dawn-hub/Views/AppView/index.js b/app/dawn-hub/Views/AppView/index.js new file mode 100644 index 000000000..72e49cad6 --- /dev/null +++ b/app/dawn-hub/Views/AppView/index.js @@ -0,0 +1,19 @@ +/** + * Point of entry index.js file for Dawn Hub App View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { buildReduxHOC } from '../../../shared-hub/utils'; + +import AppView from './AppView'; +import setToast from '../../../shared-hub/actions/ToastActions'; + +export default buildReduxHOC(['toast'], { setToast }, AppView); diff --git a/app/dawn-hub/Views/OnboardingView/OnboardingConstants.js b/app/dawn-hub/Views/OnboardingView/OnboardingConstants.js new file mode 100644 index 000000000..71eb1e4cb --- /dev/null +++ b/app/dawn-hub/Views/OnboardingView/OnboardingConstants.js @@ -0,0 +1,37 @@ +/** + * Constants for Dawn Hub + * These constants map the onboarding step numbers to more memorable names + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +export const ONBOARDING = 'onboarding'; +export const WELCOME = '0'; +export const LOGIN = '1'; +export const BLOCK_SETTINGS = '2'; +export const CHOOSE_DEFAULT_SEARCH = '3'; +export const CHOOSE_PLAN = '4'; +export const SUCCESS = '5'; + +// Setup Step Constants + +// Step 1 +export const SIGN_IN_SUCCESSFUL = '1'; +export const CREATE_ACCOUNT_SUCCESSFUL = '2'; +export const SKIP_ACCOUNT_CREATION = '3'; + +// Step 4 +export const FREE_USER_NO_TRIAL = '1'; +export const FREE_USER_PLUS_TRIAL = '2'; +export const FREE_USER_PLUS_SUBSCRIPTION = '3'; +export const FREE_USER_PREMIUM_SUBSCRIPTION = '4'; +export const PLUS_SUBSCRIBER_KEEP_SUBSCRIPTION = '5'; +export const PLUS_SUBSCRIBER_PREMIUM_SUBSCRIPTION = '6'; +export const PREMIUM_SUBSCRIBER_KEEP_SUBSCRIPTION = '7'; diff --git a/app/dawn-hub/Views/OnboardingView/OnboardingView.jsx b/app/dawn-hub/Views/OnboardingView/OnboardingView.jsx new file mode 100644 index 000000000..b09b24e74 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingView/OnboardingView.jsx @@ -0,0 +1,72 @@ +/** + * Dawn Hub root onboarding flow component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { Route } from 'react-router-dom'; + +import StepProgressBar from '../OnboardingViews/StepProgressBar'; +import StepNavigator from '../OnboardingViews/StepNavigator'; +import { + BLOCK_SETTINGS, + CHOOSE_DEFAULT_SEARCH, + CHOOSE_PLAN, + SUCCESS +} from './OnboardingConstants'; + +/** + * A Functional React component for rendering the Dawn Hub onboarding flow + * @return {JSX} JSX for rendering the Dawn Hub onboarding flow + * @memberof DawnHubViews + */ +const OnboardingView = (props) => { + const { sendMountActions, steps } = props; + + const getScreenContainerClassNames = index => ClassNames('OnboardingView__screenContainer', { + step2: index === BLOCK_SETTINGS, + step3: index === CHOOSE_DEFAULT_SEARCH, + step4: index === CHOOSE_PLAN + }); + + return ( +
+
+ {steps.map(step => ( + ( +
+ {(step.index !== SUCCESS) && } + +
+ )} + /> + ))} +
+
+ ); +}; + +// PropTypes ensure we pass required props of the correct type +OnboardingView.propTypes = { + steps: PropTypes.arrayOf(PropTypes.shape({ + index: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + bodyComponents: PropTypes.arrayOf(PropTypes.elementType.isRequired).isRequired, + })).isRequired, + sendMountActions: PropTypes.bool.isRequired, +}; + +export default OnboardingView; diff --git a/app/dawn-hub/Views/OnboardingView/OnboardingView.scss b/app/dawn-hub/Views/OnboardingView/OnboardingView.scss new file mode 100644 index 000000000..9e5972221 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingView/OnboardingView.scss @@ -0,0 +1,15 @@ +.OnboardingView__screenContainer { + max-width: 724px; + margin: 0 auto; + padding: 0 20px; + + &.step2 { + max-width: 714px; + } + &.step3 { + max-width: 728px; + } + &.step4 { + max-width: 910px; + } +} diff --git a/app/dawn-hub/Views/OnboardingView/OnboardingViewContainer.jsx b/app/dawn-hub/Views/OnboardingView/OnboardingViewContainer.jsx new file mode 100644 index 000000000..0c86c1414 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingView/OnboardingViewContainer.jsx @@ -0,0 +1,161 @@ +/** + * Dawn Hub Onboarding View Container + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import QueryString from 'query-string'; +import OnboardingView from './OnboardingView'; + +import { BLOCKING_POLICY_RECOMMENDED } from '../../../shared-hub/constants/BlockingPolicyConstants'; + +// Component Views +import WelcomeView from '../OnboardingViews/Step0_WelcomeView'; +import Step1_CreateAccountView from '../OnboardingViews/Step1_CreateAccountView'; +import BlockSettingsView from '../OnboardingViews/Step2_BlockSettingsView'; +import ChooseDefaultSearchView from '../OnboardingViews/Step3_ChooseDefaultSearchView'; +import ChoosePlanView from '../OnboardingViews/Step4_ChoosePlanView'; +import SuccessView from '../OnboardingViews/Step5_SuccessView'; +import { + ONBOARDING, + WELCOME, + LOGIN, + BLOCK_SETTINGS, + CHOOSE_DEFAULT_SEARCH, + CHOOSE_PLAN, + SUCCESS +} from './OnboardingConstants'; + +const justInstalled = (QueryString.parse(window.location.search).justInstalled === 'true') || false; + +/** + * @class Wrap the root onboarding flow view of the Dawn Hub + * @extends Component + * @memberof DawnHubContainers + */ +class OnboardingViewContainer extends Component { + constructor(props) { + super(props); + this.state = { + sendMountActions: false, + }; + + const { history } = this.props; + history.push(`/${ONBOARDING}/${WELCOME}`); + + // TODO verify what document title we should use + const title = t('ghostery_dawn_onboarding_page_title'); + window.document.title = title; + + // TODO modify this as needed + const { actions, setup } = this.props; + actions.setSetupStep({ setup_step: 7, origin: ONBOARDING }); + actions.initSetupProps(setup); + + // TODO modify this as needed + const { origin, pathname, hash } = window.location; + window.history.pushState({}, '', `${origin}${pathname}${hash}`); + + // Only set settings to defaults if the user has just installed the browser + if (justInstalled) { + this.state = { + sendMountActions: true + }; + actions.setSetupStep({ + setup_step: 8, + origin: ONBOARDING + }); + actions.setBlockingPolicy({ blockingPolicy: BLOCKING_POLICY_RECOMMENDED }); + actions.setAntiTracking({ enable_anti_tracking: true }); // covered + actions.setAdBlock({ enable_ad_block: true }); // covered + actions.setSmartBlocking({ enable_smart_block: true }); // covered + } + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the root view of the onboarding flow of the Dawn Hub app + */ + render() { + const { sendMountActions } = this.state; + const steps = [ + { + index: WELCOME, + path: `/${ONBOARDING}/${WELCOME}`, + bodyComponents: [WelcomeView], + }, + { + index: LOGIN, + path: `/${ONBOARDING}/${LOGIN}`, + bodyComponents: [Step1_CreateAccountView], + }, + { + index: BLOCK_SETTINGS, + path: `/${ONBOARDING}/${BLOCK_SETTINGS}`, + bodyComponents: [BlockSettingsView], + }, + { + index: CHOOSE_DEFAULT_SEARCH, + path: `/${ONBOARDING}/${CHOOSE_DEFAULT_SEARCH}`, + bodyComponents: [ChooseDefaultSearchView], + }, + { + index: CHOOSE_PLAN, + path: `/${ONBOARDING}/${CHOOSE_PLAN}`, + bodyComponents: [ChoosePlanView, Step1_CreateAccountView], + }, + { + index: SUCCESS, + path: `/${ONBOARDING}/${SUCCESS}`, + bodyComponents: [SuccessView], + } + ]; + + return ( +
+ +
+ ); + } +} + +// PropTypes ensure we pass required props of the correct type +// Note: isRequired is not needed when a prop has a default value +OnboardingViewContainer.propTypes = { + setup: PropTypes.shape({ + blockingPolicy: PropTypes.string, + enable_anti_tracking: PropTypes.bool, + enable_ad_block: PropTypes.bool, + enable_smart_block: PropTypes.bool, + }), + actions: PropTypes.shape({ + initSetupProps: PropTypes.func.isRequired, + setSetupStep: PropTypes.func.isRequired, + setSetupComplete: PropTypes.func.isRequired, + setBlockingPolicy: PropTypes.func.isRequired, + setAntiTracking: PropTypes.func.isRequired, + setAdBlock: PropTypes.func.isRequired, + setSmartBlocking: PropTypes.func.isRequired, + }).isRequired, +}; + +// Default props used throughout the Onboarding flow +OnboardingViewContainer.defaultProps = { + setup: { + blockingPolicy: BLOCKING_POLICY_RECOMMENDED, + enable_anti_tracking: true, + enable_ad_block: true, + enable_smart_block: true, + }, +}; + +export default OnboardingViewContainer; diff --git a/app/dawn-hub/Views/OnboardingView/index.js b/app/dawn-hub/Views/OnboardingView/index.js new file mode 100644 index 000000000..5ef1e049f --- /dev/null +++ b/app/dawn-hub/Views/OnboardingView/index.js @@ -0,0 +1,45 @@ +/** + * Point of entry index.js file for Dawn Hub Onboarding View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { withRouter } from 'react-router-dom'; + +import { buildReduxHOC } from '../../../shared-hub/utils'; + +import OnboardingViewContainer from './OnboardingViewContainer'; +import { + initSetupProps, + setSetupStep, + setSetupComplete +} from '../../../shared-hub/actions/SetupLifecycleActions'; +import { + setAdBlock, + setAntiTracking, + setSmartBlocking +} from '../../../shared-hub/actions/AntiSuiteActions'; +import setBlockingPolicy from '../../../shared-hub/actions/BlockingPolicyActions'; + +export default withRouter(buildReduxHOC( + ['setup', 'account'], + { + initSetupProps, + setSetupStep, + setSetupComplete, + + setBlockingPolicy, + + setAntiTracking, + setAdBlock, + setSmartBlocking, + }, + OnboardingViewContainer +)); diff --git a/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.jsx b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.jsx new file mode 100644 index 000000000..3e591cba3 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.jsx @@ -0,0 +1,46 @@ +/** + * Dawn Hub onboarding flow Welcome View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { NavLink } from 'react-router-dom'; +import { LOGIN, WELCOME } from '../../OnboardingView/OnboardingConstants'; + +/** + * A Functional React component for rendering the Browser Welcome View + * @return {JSX} JSX for rendering the Browser Welcome View of the Hub app + * @memberof DawnHubViews + */ +const WelcomeView = (props) => { + const { actions } = props; + const { setSetupStep } = actions; + return ( +
+
{t('ghostery_dawn_onboarding_welcome')}
+
{t('ghostery_dawn_onboarding_welcome_message')}
+ + setSetupStep({ setup_step: LOGIN, origin: WELCOME })}> + {t('ghostery_dawn_onboarding_lets_do_this')} + +
+ ); +}; + +export default WelcomeView; + +// PropTypes ensure we pass required props of the correct type +WelcomeView.propTypes = { + actions: PropTypes.shape({ + setSetupStep: PropTypes.func.isRequired + }).isRequired, +}; diff --git a/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.scss b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.scss new file mode 100644 index 000000000..f25adcef6 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.scss @@ -0,0 +1,72 @@ +/** + * WelcomeView Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +.WelcomeView__container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.WelcomeView__title { + margin-top: 125px; + font-size: 24px; + font-weight: 600; + line-height: 2.33; + text-align: center; +} + +.WelcomeView__subtitle { + margin-bottom: 20px; + max-width: 495px; + font-size: 18px; + line-height: 2.33; + text-align: center; +} + +.WelcomeView__rocketShip { + margin-bottom: 84px; + height: 330px; + width: 545px; +} + +.WelcomeView__ctaButton { + display: flex; + justify-content: center; + margin: 0 auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + $ghosty_blue + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + color: $white; + } + color: $white; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/WelcomeView.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/WelcomeView.test.jsx new file mode 100644 index 000000000..be3a03b9d --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/WelcomeView.test.jsx @@ -0,0 +1,37 @@ +/** + * Dawn Hub onboarding flow Welcome View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router'; +import WelcomeView from '../WelcomeView'; + +const noop = () => {}; + +describe('app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.test.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('Welcome View is rendered correctly', () => { + const initialState = { + actions: { + setSetupStep: noop + } + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/__snapshots__/WelcomeView.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/__snapshots__/WelcomeView.test.jsx.snap new file mode 100644 index 000000000..d98093c3e --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/__tests__/__snapshots__/WelcomeView.test.jsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.test.jsx Snapshot tests with react-test-renderer Welcome View is rendered correctly 1`] = ` +
+
+ ghostery_dawn_onboarding_welcome +
+
+ ghostery_dawn_onboarding_welcome_message +
+ + + + ghostery_dawn_onboarding_lets_do_this + + +
+`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/index.js b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/index.js new file mode 100644 index 000000000..58006bd9d --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step0_WelcomeView/index.js @@ -0,0 +1,22 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Welcome View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { buildReduxHOC } from '../../../../shared-hub/utils'; +import WelcomeView from './WelcomeView'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; + +const actionCreators = { + setSetupStep, +}; + +export default buildReduxHOC([], actionCreators, WelcomeView); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx new file mode 100644 index 000000000..21e40f110 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx @@ -0,0 +1,231 @@ +/** + * Browser Create Account Form + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 ToggleCheckbox from '../../../../shared-components/ToggleCheckbox'; + +/** + * A Functional React component for rendering the Browser Create Account View + * @return {JSX} JSX for rendering the Browser Create Account View of the Dawn Hub app + * @memberof DawnHubViews + */ +export const Step1_CreateAccountForm = (props) => { + const { + email, + emailError, + confirmEmail, + confirmEmailError, + firstName, + lastName, + password, + passwordInvalidError, + passwordLengthError, + confirmPassword, + confirmPasswordError, + legalConsentChecked, + legalConsentNotCheckedError, + handleLegalConsentCheckboxChange, + handleInputChange, + handleSubmit, + } = props; + + const emailInputClassNames = ClassNames('Step1_CreateAccountForm__inputBox', { + error: emailError, + }); + const confirmInputClassNames = ClassNames('Step1_CreateAccountForm__inputBox', { + error: confirmEmailError, + }); + const passwordInputClassNames = ClassNames('Step1_CreateAccountForm__inputBox', { + error: passwordInvalidError || passwordLengthError, + }); + const legalConsentClassNames = ClassNames('Step1_CreateAccountForm__legalConsentCheckedLabel', { + error: legalConsentNotCheckedError + }); + + return ( +
+
+
+ + + {emailError && ( +
+
+ {t('please_enter_a_valid_email')} +
+
+ )} +
+
+ + + {confirmEmailError && ( +
+
+ {t('your_email_do_not_match')} +
+
+ )} +
+
+
+
+ + +
+
+ + +
+
+
+
+ + + {passwordInvalidError && ( +
+
+ {t('hub_create_account_label_password_invalid')} +
+
+ )} + {passwordLengthError && ( +
+
+ {t('hub_create_account_label_password_invalid_length')} +
+
+ )} +
+
+ + + {confirmPasswordError && ( +
+
+ {t('hub_create_account_confirm_password_do_not_match')} +
+
+ )} +
+
+
+
+
+ + +
+
+
+
+ +
+
+ ); +}; + +// PropTypes ensure we pass required props of the correct type +Step1_CreateAccountForm.propTypes = { + email: PropTypes.string.isRequired, + emailError: PropTypes.bool.isRequired, + confirmEmail: PropTypes.string.isRequired, + confirmEmailError: PropTypes.bool.isRequired, + firstName: PropTypes.string.isRequired, + lastName: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + confirmPassword: PropTypes.string.isRequired, + passwordInvalidError: PropTypes.bool.isRequired, + passwordLengthError: PropTypes.bool.isRequired, + handleInputChange: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired, +}; + +export default Step1_CreateAccountForm; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss new file mode 100644 index 000000000..43bea6263 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss @@ -0,0 +1,104 @@ +/** + * Step1_CreateAccountForm Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +.Step1_CreateAccountForm--addPaddingTop { + padding-top: 20px; +} +.Step1_CreateAccountForm__inputLabel { + font-size: 14px; + font-weight: 500; + line-height: 2.86; + text-transform: uppercase; + color: #4a4a4a; +} +.Step1_CreateAccountForm__inputBox { + margin-bottom: 20px; + font-size: 14; + line-height: 24px; + color: #4a4a4a; + + // Foundation Overrides + border-radius: 6px; + box-shadow: none; + border: 1px solid #9b9b9b; + + &::placeholder { + color: #c4ced1; + } +} +.Step1_CreateAccountForm__inputBox.error { + border-color: $color-create-account-form-error-red; +} +.Step1_CreateAccountForm__inputErrorContainer { + position: relative; + + > .Step1_CreateAccountForm__inputError { + position: absolute; + margin-top: -15px; + color: red; + } +} +.Step1_CreateAccountForm__checkboxContainer { + margin-top: 36px; +} +.Step1_CreateAccountForm__legalConsentCheckedLabel { + font-size: 14px; + margin-top: 13px; + @include breakpoint(small down) { + width: 290px; + } + + &.error { + color: red; + } +} +.Step1_CreateAccountForm__ctaButtonContainer { + display: flex; + justify-content: center; + + .Step1_CreateAccountForm__ctaButton { + margin: 48px auto 0 auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; + } +} +@media only screen and (max-width: 740px) { + .Step1_CreateAccountForm__checkboxContainer { + display: flex; + align-items: center; + padding-right: 12px; + justify-content: center; + } +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx new file mode 100644 index 000000000..79a54ff76 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx @@ -0,0 +1,237 @@ +/** + * Create Account Form Container + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021, 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { + validateEmail, + validatePassword, + validateEmailsMatch, + validateConfirmEmail, + validatePasswordsMatch +} from '../../../../panel/utils/utils'; +import BrowserCreateAccountForm from './Step1_CreateAccountForm'; + +/** + * @class Implement the Create Account View for the Dawn Hub + * @extends Component + * @memberof DawnHubContainers + */ +class CreateAccountFormContainer extends Component { + constructor(props) { + super(props); + this.state = { + email: '', + emailError: false, + confirmEmail: '', + confirmEmailError: false, + firstName: '', + lastName: '', + legalConsentChecked: false, + legalConsentNotCheckedError: false, + isUpdatesChecked: false, + password: '', + passwordInvalidError: false, + passwordLengthError: false, + confirmPassword: '', + confirmPasswordError: '', + validateInput: false, + }; + + const { actions } = this.props; + actions.setToast({ + toastMessage: '', + toastClass: '', + }); + } + + /** + * Update input values by updating state. + * @param {Object} event the 'change' event + */ + _handleInputChange = (event) => { + const { name, value } = event.target; + this.setState({ [name]: value }); + + const { validateInput } = this.state; + if (!validateInput) { + return; + } + + switch (name) { + case 'email': { + const emailIsValid = value && validateEmail(value); + this.setState({ + emailError: !emailIsValid, + }); + break; + } + case 'confirmEmail': { + const { email } = this.state; + const confirmIsValid = value && validateEmailsMatch(email, value); + this.setState({ + confirmEmailError: !confirmIsValid, + }); + break; + } + case 'password': { + const passwordIsValid = value && validatePassword(value); + const invalidChars = !passwordIsValid && value.length >= 8 && value.length <= 50; + const invalidLength = !passwordIsValid && !invalidChars; + this.setState({ + passwordInvalidError: invalidChars, + passwordLengthError: invalidLength, + }); + break; + } + case 'confirmPassword': { + const { password } = this.state; + const confirmPasswordIsValid = value && validatePasswordsMatch(password, value); + this.setState({ + confirmPasswordError: !confirmPasswordIsValid, + }); + break; + } + default: break; + } + } + + /** + * Update legal consent checkbox value by updating state + */ + _handleLegalConsentCheckboxChange = () => { + this.setState(prevState => ({ legalConsentChecked: !prevState.legalConsentChecked })); + } + + /** + * Handle creating an account, but validate the data first. + * @param {Object} event the 'submit' event + */ + _handleCreateAccountAttempt = (event) => { + event.preventDefault(); + const { + email, + confirmEmail, + firstName, + lastName, + legalConsentChecked, + password, + confirmPassword, + } = this.state; + const emailIsValid = email && validateEmail(email); + const confirmIsValid = confirmEmail && validateConfirmEmail(email, confirmEmail); + const passwordIsValid = password && validatePassword(password); + const invalidChars = !passwordIsValid && password.length >= 8 && password.length <= 50; + const invalidLength = !passwordIsValid && !invalidChars; + const confirmPasswordError = password !== confirmPassword; + + this.setState({ + emailError: !emailIsValid, + confirmEmailError: !confirmIsValid, + legalConsentNotCheckedError: !legalConsentChecked, + passwordInvalidError: invalidChars, + passwordLengthError: invalidLength, + confirmPasswordError, + validateInput: true, + }); + + const { actions } = this.props; + if (!emailIsValid || !confirmIsValid || !legalConsentChecked || !passwordIsValid || confirmPasswordError) { + actions.setToast({ + toastMessage: t('ghostery_dawn_onboarding_toast_fix_errors'), + toastClass: 'alert' + }); + return; + } + + actions.setToast({ + toastMessage: '', + toastClass: '' + }); + actions.register(email, confirmEmail, firstName, lastName, password).then((success) => { + if (success) { + // User is automatically logged in, and redirected to the logged in view of BrowserCreateAccountForm + actions.getUser().then(() => { + actions.subscribeToEmailList('global'); + }); + // Toggle legal consent checked here + actions.setToast({ + toastMessage: t('hub_create_account_toast_success'), + toastClass: 'success' + }); + } else { + actions.setToast({ + toastMessage: t('hub_create_account_toast_error'), + toastClass: 'alert' + }); + } + }); + } + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the Create Account View of the Hub app + */ + render() { + // const { user } = this.props; + const { + email, + emailError, + confirmEmail, + confirmEmailError, + firstName, + lastName, + legalConsentChecked, + legalConsentNotCheckedError, + isUpdatesChecked, + password, + passwordInvalidError, + passwordLengthError, + confirmPassword, + confirmPasswordError + } = this.state; + + return ( + + ); + } +} + +// PropTypes ensure we pass required props of the correct type +CreateAccountFormContainer.propTypes = { + actions: PropTypes.shape({ + setToast: PropTypes.func.isRequired, + register: PropTypes.func.isRequired, + getUser: PropTypes.func.isRequired, + }).isRequired, +}; + +export default CreateAccountFormContainer; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/Step1_CreateAccountForm.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/Step1_CreateAccountForm.test.jsx new file mode 100644 index 000000000..9810bd29d --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/Step1_CreateAccountForm.test.jsx @@ -0,0 +1,88 @@ +/** + * Create Account View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import { Step1_CreateAccountForm } from '../Step1_CreateAccountForm'; + + +jest.mock('../../../../../shared-components/ToggleCheckbox', () => { + const ToggleCheckbox = () =>
; + return ToggleCheckbox; +}); + +const noop = () => {}; +describe('app/hub/Views/Step1_CreateAccountForm component', () => { + const initialState = { + email: 'test@example.com', + emailError: false, + confirmEmail: 'test@example.com', + confirmEmailError: false, + firstName: 'First', + lastName: 'Last', + isUpdatesChecked: true, + legalConsentChecked: true, + password: '', + confirmPassword: '', + passwordInvalidError: false, + passwordLengthError: false, + handleInputChange: noop, + handleUpdatesCheckboxChange: noop, + handleLegalConsentCheckboxChange: noop, + handleSubmit: jest.fn(), + }; + + describe('Snapshot tests with react-test-renderer', () => { + test('create account view is rendered correctly', () => { + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('the happy path of the component', () => { + const component = shallow(); + + expect(initialState.handleSubmit.mock.calls.length).toBe(0); + component.find('form').simulate('submit'); + expect(initialState.handleSubmit.mock.calls.length).toBe(1); + }); + + test('the sad path of the component with errors', () => { + const initialStateFail = { + ...initialState, + emailError: true, + confirmEmail: 'badConfirmEmail@example.com', + confirmEmailError: true, + isUpdatesChecked: false, + legalConsentChecked: false, + password: 'password', + confirmPassword: 'password', + passwordInvalidError: true, + passwordLengthError: true, + } + + const component = shallow(); + component.find('form').simulate('submit'); + expect(component.find('.Step1_CreateAccountForm__inputErrorContainer').length).toBe(4); + expect(component).toMatchSnapshot(); + + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/__snapshots__/Step1_CreateAccountForm.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/__snapshots__/Step1_CreateAccountForm.test.jsx.snap new file mode 100644 index 000000000..65c1003cf --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/__tests__/__snapshots__/Step1_CreateAccountForm.test.jsx.snap @@ -0,0 +1,175 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/hub/Views/Step1_CreateAccountForm component Shallow snapshot tests rendered with Enzyme the sad path of the component with errors 1`] = `ShallowWrapper {}`; + +exports[`app/hub/Views/Step1_CreateAccountForm component Snapshot tests with react-test-renderer create account view is rendered correctly 1`] = ` +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+ +
+
+
+
+ +
+ +`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js new file mode 100644 index 000000000..4a0de7ea9 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js @@ -0,0 +1,27 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Create Account Form + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { buildReduxHOC } from '../../../../shared-hub/utils'; +import Step1_CreateAccountFormContainer from './Step1_CreateAccountFormContainer'; +import { register, getUser, subscribeToEmailList } from '../../../../Account/AccountActions'; +import { setToast } from '../../../../hub/Views/AppView/AppViewActions'; + +const stateSlices = ['account']; +const actionCreators = { + setToast, + register, + getUser, + subscribeToEmailList +}; + +export default buildReduxHOC(stateSlices, actionCreators, Step1_CreateAccountFormContainer); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx new file mode 100644 index 000000000..2d8374ef9 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx @@ -0,0 +1,195 @@ +/** + * Browser Create Account View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Fragment, useState } from 'react'; +import { NavLink } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import Step1_LogInForm from '../Step1_LogInForm'; +import Step1_CreateAccountForm from '../Step1_CreateAccountForm'; +import globals from '../../../../../src/classes/Globals'; +import { + LOGIN, + ONBOARDING, + SIGN_IN_SUCCESSFUL, + CREATE_ACCOUNT_SUCCESSFUL, + SKIP_ACCOUNT_CREATION +} from '../../OnboardingView/OnboardingConstants'; + +const SIGN_IN = 'SIGN_IN'; +const CREATE_ACCOUNT = 'CREATE_ACCOUNT'; + +const faqList = [ + { + icon: 'ghosty-shield.svg', + label: t('ghostery_dawn_onboarding_private_by_design'), + description: t('ghostery_dawn_onboarding_private_by_design_description'), + }, + { + icon: 'ghosty-box.svg', + label: t('ghostery_dawn_onboarding_can_i_remove_my_account'), + description: t('ghostery_dawn_onboarding_can_i_remove_my_account_description'), + } +]; + +const renderFAQListItem = (icon, label, description) => ( +
+
+ +
+
+
{label}
+
{description}
+
+
+); + +/** + * A Functional React component for rendering the Browser Create Account View + * @return {JSX} JSX for rendering the Browser Create Account View of the Dawn Hub app + * @memberof DawnHubViews + */ +const Step1_CreateAccountView = (props) => { + const { actions, step, user } = props; + const { setSetupStep, setToast } = actions; + const email = user && user.email; + + const [view, setView] = useState(CREATE_ACCOUNT); + + const handleSkipButton = (dawn_setup_number) => { + setSetupStep({ + setup_step: LOGIN, + dawn_setup_number, + origin: ONBOARDING, + }); + setToast({ + toastMessage: '', + toastClass: '' + }); + }; + + const handleNextOnSelectPlanStep = () => { + const { prev } = props; + + setSetupStep({ + setup_step: LOGIN, + dawn_setup_number: CREATE_ACCOUNT_SUCCESSFUL, + origin: ONBOARDING, + }); + + setToast({ + toastMessage: '', + toastClass: '' + }); + + prev(); + }; + + const renderSkipLink = () => ( +
+
+
+ handleSkipButton(SKIP_ACCOUNT_CREATION)}> + {t('ghostery_dawn_onboarding_skip')} + +
+
+ ); + + const subtitle = (step === LOGIN) + ? t('ghostery_dawn_onboarding_sync_settings') + : t('ghostery_dawn_onboarding_create_account_for_trial'); + + return (user ? ( +
+
{t('ghostery_dawn_onboarding_you_are_signed_in_as')}
+
{email}
+
+ {step === LOGIN && ( + handleSkipButton(SIGN_IN_SUCCESSFUL)}> + {t('next')} + + )} + {step !== LOGIN && ( +
handleNextOnSelectPlanStep()}> + {t('next')} +
+ )} +
+
+ ) : ( +
+ {step !== LOGIN && ( +
+
+ + + {t('ghostery_dawn_onboarding_back_to_search_selection')} + +
+
+ )} + {view === CREATE_ACCOUNT && ( +
{t('ghostery_dawn_onboarding_create_a_ghostery_account')}
+ )} + {view === SIGN_IN && ( +
{t('sign_in')}
+ )} +
{subtitle}
+
+ {view === CREATE_ACCOUNT && ( +
setView(SIGN_IN)}>{t('ghostery_dawn_onboarding_already_have_account')}
+ )} + {view === SIGN_IN && ( +
setView(CREATE_ACCOUNT)}>{t('ghostery_dawn_onboarding_create_an_account')}
+ )} +
+ {view === CREATE_ACCOUNT ? ( + + {/* eslint-disable-next-line react/jsx-pascal-case */} + + {(step === LOGIN) && renderSkipLink()} +
+ {faqList.map(item => renderFAQListItem(item.icon, item.label, item.description))} +
+ +
+ ) : ( + + {/* eslint-disable-next-line react/jsx-pascal-case */} + + {(step === LOGIN) && renderSkipLink()} + + )} +
+ )); +}; + +// PropTypes ensure we pass required props of the correct type +Step1_CreateAccountView.propTypes = { + user: PropTypes.shape({ + email: PropTypes.string, + }), +}; + +// Default props used in the Plus View +Step1_CreateAccountView.defaultProps = { + user: { + email: '' + }, +}; + +export default Step1_CreateAccountView; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss new file mode 100644 index 000000000..10396b3d7 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss @@ -0,0 +1,293 @@ +/** + * Step1_CreateAccountView Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +$color-create-account-form-error-red: #e74055; + +// Browser Create Account View +.Step1_CreateAccountView { + padding-top: 80px; + padding-bottom: 40px; +} + +.CreateAccountView__relativeContainer { + position: relative; + left: 30px; + top: -70px; +} + +.CreateAccountView__caret.left { + margin: 7px auto 0 auto; + height: 10px; + width: 10px; + border-left: 2px solid $tundora; + border-top: 2px solid $tundora; + cursor: pointer; + transform: rotate(-45deg); +} + +.CreateAccountView__backContainer { + position: absolute; + display: flex; + margin-top: 60px; + @include breakpoint(xlarge down) { + margin-top: 22px; + } + .CreateAccountView__back { + margin-top: 8px; + font-size: 16px; + color: $tundora; + text-decoration: underline; + } +} + +.Step1_CreateAccountView__alreadySignedIn { + margin-top: 245px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.Step1_CreateAccountView__email { + font-size: 24px; + color: $ghosty-blue; +} +.Step1_CreateAccountView__ctaButtonContainer { + display: flex; + justify-content: center; + + .Step1_CreateAccountView__ctaButton { + margin: 48px auto 0 auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; + } +} +.Step1_CreateAccountView__title { + display: flex; + justify-content: center; + font-size: 24px; + font-weight: 600; + @include breakpoint(small down) { + margin: auto; + max-width: 237px; + text-align: center; + } +} +.Step1_CreateAccountView__subtitle { + margin: 10px auto 0 auto; + display: flex; + justify-content: center; + font-size: 18px; + text-align: center; + max-width: 600px; + @include breakpoint(small down) { + margin: auto; + max-width: 237px; + text-align: center; + line-height: 2.33; + } +} +.Step1_CreateAccountView__alreadyHaveAccount { + margin-top: 38px; + font-size: 16px; + color: $ghosty-blue; + text-decoration: underline; + cursor: pointer; + @include breakpoint(small down) { + margin: 38px auto 0 auto; + text-align: center; + + &:nth-child(2) { + margin: 0; + } + } +} +.Step1_CreateAccountView--addMarginSide { + margin-left: 10px; + margin-right: 10px; +} +.Step1_CreateAccountView--addMarginBottom { + margin-bottom: 20px; +} +.Step1_CreateAccountView__headerImage { + max-width: 180px; +} +.Step1_CreateAccountView__headerTitle h3 { + font-size: 24px; + line-height: 1.75; + color: #4a4a4a; + margin: 0 25px; +} +.Step1_CreateAccountView__inputBox.error { + // Foundation Overrides + box-shadow: none; + border-color: 2px solid $color-create-account-form-error-red; +} +.Step1_CreateAccountView__inputBox:focus { + // Foundation Overrides + box-shadow: none; + border-color: $color-create-account-form-error-red; +} +.Step1_CreateAccountView__inputError { + font-size: 12; + line-height: 14px; + color: $color-create-account-form-error-red; + margin-bottom: 13px; +} +.Step1_CreateAccountView__link { + font-size: 14px; + line-height: 30px; +} +.Step1_CreateAccountView__termsStatement { + font-size: 12px; +} +.Step1_CreateAccountView__button { + min-width: 180px; +} +.Step1_CreateAccountView__inputLabel { + flex-grow: 1; // ensure left-justify alignment in single column layout + padding-top: 10px; // align with checkbox top +} +.Step1_CreateAccountView__inputLabel.error{ + color: $color-create-account-form-error-red; +} +.Step1_CreateAccountView__skip { + margin-top: -37px; + float: right; + font-size: 16px; + color: $ghosty-blue; + text-decoration: underline; + cursor: pointer; + @include breakpoint(small down) { + margin: 39px auto 0 auto; + display: flex; + justify-content: center; + float: none; + } +} +.Step1_CreateAccountView__learnMoreContainer { + margin: 25px auto 0 auto; + width: 251px; + text-align: center; +} +.Step1_CreateAccountView__learnMore { + font-size: 16px; + color: $ghosty-blue; + text-decoration: underline; + cursor: pointer; +} +.Step1_CreateAccountView__FAQContainer { + margin-top: 48px; + padding-top: 20px; + @include breakpoint(small down) { + margin-top: 42px; + text-align: center; + } + @include breakpoint(large up) { + width: 108%; + margin-left: -4%; + } +} +.Step1_CreateAccountView__faqItemContainer { + margin-bottom: 30px; +} +.Step1_CreateAccountView__faqIcon { + height: 71px; + width: 71px; + min-height: 71px; + min-width: 71px; + margin-bottom: 10px; +} +.Step1_CreateAccountView__faqItemTextContainer { + display: flex; + flex-direction: column; +} +.Step1_CreateAccountView__faqItemLabel { + font-size: 18px; + font-weight: 600; + color: #4a4a4a; +} +.Step1_CreateAccountView__faqItemDescription { + margin-top: 10px; + max-width: 607px; + font-size: 16px; + color: #4a4a4a; +} +.Step1_CreateAccountView__faqItemDescription { + margin-top: 10px; + max-width: 607px; + font-size: 16px; + color: #4a4a4a; + @include breakpoint(small down) { + margin: 16px auto 0 auto; + width: 245px; + } +} +.Step1_CreateAccountView__privacyPolicyLink { + font-size: 16px; + color: $ghosty-blue; + text-decoration: underline; + cursor: pointer; + @include breakpoint(small down) { + margin: auto; + max-width: 271px; + text-align: center; + } +} +@media only screen and (max-width: 740px) { + .Step1_CreateAccountView { + margin-bottom: 40px; + padding-top: 80px; + } + .Step1_CreateAccountView__header { + flex-direction: column; + } + .Step1_CreateAccountView__headerTitle { + padding-top: 30px; + text-align: center; + } + .Step1_CreateAccountView__submit { + display: flex; + justify-content: center; + } + .Step1_CreateAccountView__linkContainer { + text-align: center; + } +} +@media only screen and (max-width: 500px) { + .Step1_CreateAccountView__headerTitle h3 { + font-size: 20px; + margin: 0 10px; + } +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/Step1_CreateAccountView.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/Step1_CreateAccountView.test.jsx new file mode 100644 index 000000000..7ab8e56a5 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/Step1_CreateAccountView.test.jsx @@ -0,0 +1,61 @@ +/** + * Create Account View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import Step1_CreateAccountView from '../Step1_CreateAccountView'; + +jest.mock('../../Step1_CreateAccountForm', () => { + const CreateAccountForm = () =>
; + return CreateAccountForm; +}); + +jest.mock('../../Step1_LogInForm', () => { + const LogInForm = () =>
; + return LogInForm; +}); + +const noop = () => {}; +describe('app/hub/Views/Step1_CreateAccountView component', () => { + const initialState = { + user: null, + actions: { + setSetupStep: noop + } + }; + describe('Snapshot tests with react-test-renderer', () => { + test('Create Account Form view is rendered correctly', () => { + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('Create Account Form view is rendered correctly when user is logged in', () => { + const plusUserState = { + ...initialState, + user: { + plusAccess: true + } + }; + + const component = shallow(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/__snapshots__/Step1_CreateAccountView.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/__snapshots__/Step1_CreateAccountView.test.jsx.snap new file mode 100644 index 000000000..41f969631 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/__tests__/__snapshots__/Step1_CreateAccountView.test.jsx.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/hub/Views/Step1_CreateAccountView component Snapshot tests with react-test-renderer Create Account Form view is rendered correctly 1`] = ` +
+ +
+ ghostery_dawn_onboarding_create_a_ghostery_account +
+
+ ghostery_dawn_onboarding_create_account_for_trial +
+
+
+ ghostery_dawn_onboarding_already_have_account +
+
+
+
+
+
+ +
+
+
+ ghostery_dawn_onboarding_private_by_design +
+
+ ghostery_dawn_onboarding_private_by_design_description +
+
+
+
+
+ +
+
+
+ ghostery_dawn_onboarding_can_i_remove_my_account +
+
+ ghostery_dawn_onboarding_can_i_remove_my_account_description +
+
+
+
+ +
+`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js new file mode 100644 index 000000000..2800c1374 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js @@ -0,0 +1,24 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Create Account View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { buildReduxHOC } from '../../../../shared-hub/utils'; +import Step1_CreateAccountView from './Step1_CreateAccountView'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; +import setToast from '../../../../shared-hub/actions/ToastActions'; + +const actionCreators = { + setSetupStep, + setToast +}; + +export default buildReduxHOC(['account'], actionCreators, Step1_CreateAccountView); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx new file mode 100644 index 000000000..3488783c9 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx @@ -0,0 +1,113 @@ +/** + * Browser Log In Form + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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'; + +/** + * A Functional React component for rendering the Log In Form + * @return {JSX} JSX for rendering the Log In Form of the Dawn Hub app + * @memberof DawnHubViews + */ +const Step1_LogInForm = (props) => { + const { + email, + password, + emailError, + passwordError, + handleSubmit, + handleInputChange, + handleForgotPassword, + } = props; + + const emailInputClassNames = ClassNames('Step1_LogInForm__inputBox', { + error: emailError, + }); + const passwordInputClassNames = ClassNames('Step1_LogInForm__inputBox', { + error: passwordError, + }); + + return ( +
+
+
+ + + {emailError && ( +
+ {t('please_enter_a_valid_email')} +
+ )} +
+
+
+
+ + + {passwordError && ( +
+ {t('hub_login_label_password_invalid')} +
+ )} +
+
+
+
+ +
+ {t('forgot_password')} +
+
+
+
+
+ +
+
+ ); +}; + +// PropTypes ensure we pass required props of the correct type +Step1_LogInForm.propTypes = { + email: PropTypes.string.isRequired, + password: PropTypes.string.isRequired, + emailError: PropTypes.bool.isRequired, + passwordError: PropTypes.bool.isRequired, + handleSubmit: PropTypes.func.isRequired, + handleInputChange: PropTypes.func.isRequired, +}; + +export default Step1_LogInForm; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss new file mode 100644 index 000000000..b532fa9a5 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss @@ -0,0 +1,109 @@ +/** + * Step1_LogInForm Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +.Step1_LogInForm { + margin-top: 40px; +} +.Step1_LogInForm__headerImage { + line-height: 1.5; + color: #4a4a4a; + margin: 20px 25px 0; +} +.Step1_LogInForm__inputContainer { + margin: auto; +} +.Step1_LogInForm__inputLabel { + text-align: left; + font-size: 14px; + font-weight: 500; + line-height: 2.86; + text-transform: uppercase; + color: #4a4a4a; +} +.Step1_LogInForm__inputBox { + line-height: 24px; + color: #4a4a4a; + + // Foundation Overrides + border-radius: 6px; + box-shadow: none; + border: 1px solid #9b9b9b; + + &::placeholder { + color: #c4ced1; + } +} +.Step1_LogInForm__inputBox.error { + margin-bottom: 8px; + border: 2px solid #e74055; +} +.Step1_LogInForm__inputBox:focus { + // Foundation Overrides + box-shadow: none; + border-color: #4a4a4a; +} +.Step1_LogInForm__inputError { + font-size: 12; + line-height: 14px; + color: #e74055; + margin-bottom: 13px; +} +.Step1_LogInForm__link { + font-size: 14px; + line-height: 30px; +} +.Step1_LogInForm__forgotPassword { + color: $ghosty-blue; + text-decoration: underline; + cursor: pointer; +} +.Step1_LogInForm__button { + min-width: 180px; +} +.Step1_LogInForm__ctaButtonContainer { + display: flex; + justify-content: center; + + .Step1_LogInForm__ctaButton { + margin: 48px auto 0 auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; + } +} +@media only screen and (max-width: 740px) { + .Step1_LogInForm__header { + flex-direction: column; + } +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx new file mode 100644 index 000000000..b2cd5a962 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx @@ -0,0 +1,188 @@ +/** + * Step1_LogInFormContainer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { throttle } from 'underscore'; +import { validateEmail } from '../../../../panel/utils/utils'; +import Step1_LogInForm from './Step1_LogInForm'; + +/** + * @class Implement the Browser Log In Form for the Dawn Hub + * @extends Component + * @memberof DawnHubContainers + */ +class Step1_LogInFormContainer extends Component { + constructor(props) { + super(props); + this.state = { + email: '', + password: '', + emailError: false, + passwordError: false, + validateInput: false, + }; + + const { actions } = this.props; + actions.setToast({ + toastMessage: '', + toastClass: '', + }); + } + + /** + * Update input values by updating state. + * @param {Object} event the 'change' event + */ + _handleInputChange = (event) => { + const { name, value } = event.target; + this.setState({ [name]: value }); + + const { validateInput } = this.state; + if (!validateInput) { + return; + } + + switch (name) { + case 'email': { + const emailIsValid = value && validateEmail(value); + this.setState({ + emailError: !emailIsValid, + }); + break; + } + case 'password': { + this.setState({ + passwordError: !value, + }); + break; + } + default: break; + } + } + + /** + * Handle logging in, but validate the data first. + * @param {Object} event the 'submit' event + */ + _handleLoginAttempt = (event) => { + event.preventDefault(); + const { email, password } = this.state; + const emailIsValid = email && validateEmail(email); + + this.setState({ + emailError: !emailIsValid, + passwordError: !password, + validateInput: true, + }); + + if (!emailIsValid || !password) { + return; + } + + const { actions } = this.props; + actions.setToast({ + toastMessage: '', + toastClass: '' + }); + actions.login(email, password).then((success) => { + if (success) { + const { origin, pathname, hash } = window.location; + window.history.pushState({}, '', `${origin}${pathname}${hash}`); + + actions.getUser(); + // TODO investigate whether this is needed here + actions.getUserSettings() + .then((settings) => { + const { current_theme } = settings; + return actions.getTheme(current_theme); + }); + actions.setToast({ + toastMessage: t('hub_login_toast_success'), + toastClass: 'success' + }); + // Move to second screen + } else { + actions.setToast({ + toastMessage: t('no_such_email_password_combo'), + toastClass: 'alert' + }); + } + }); + } + + _unthrottledHandleForgotPassword = (e) => { + e.preventDefault(); + const { email } = this.state; + + // validate the email and password + if (!validateEmail(email)) { + this.setState({ + emailError: true, + }); + return; + } + + const { actions } = this.props; + actions.resetPassword(email) + .then((success) => { + if (success) { + actions.setToast({ + toastMessage: t('banner_check_your_email_title'), + toastClass: 'success', + }); + } + }); + } + + // eslint-disable-next-line react/sort-comp + _handleForgotPassword = throttle(this._unthrottledHandleForgotPassword, 3000) + + /** + * React's required render function. Returns JSX + * @return {JSX} JSX for rendering the Log In View of the Hub app + */ + render() { + const { + email, + password, + emailError, + passwordError, + } = this.state; + + return ( + // eslint-disable-next-line react/jsx-pascal-case + + ); + } +} + +// PropTypes ensure we pass required props of the correct type +Step1_LogInFormContainer.propTypes = { + actions: PropTypes.shape({ + setToast: PropTypes.func.isRequired, + login: PropTypes.func.isRequired, + getUser: PropTypes.func.isRequired, + getUserSettings: PropTypes.func.isRequired, + }).isRequired, +}; + +export default Step1_LogInFormContainer; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/Step1_LoginForm.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/Step1_LoginForm.test.jsx new file mode 100644 index 000000000..c64fb0a34 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/Step1_LoginForm.test.jsx @@ -0,0 +1,75 @@ +/** + * Create Account View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import Step1_LoginForm from '../Step1_LoginForm'; + +const noop = () => {}; +describe('app/hub/Views/Step1_LoginForm component', () => { + const initialState = { + email: '', + password: '', + emailError: false, + passwordError: false, + handleSubmit: noop, + handleInputChange: noop, + handleForgotPassword: noop + }; + describe('Snapshot tests with react-test-renderer', () => { + test('Login Form view is rendered correctly', () => { + + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('the happy path of the component', () => { + const happyState = { + ...initialState, + email: 'test@example.com', + password: 'examplePassword', + handleSubmit: jest.fn(), + } + + const component = shallow(); + expect(happyState.handleSubmit.mock.calls.length).toBe(0); + component.find('form').simulate('submit'); + expect(happyState.handleSubmit.mock.calls.length).toBe(1); + }); + + test('the sad path of the component with errors', () => { + const sadState = { + ...initialState, + email: 'test@example.com', + password: 'examplePassword', + emailError: true, + passwordError: true, + handleSubmit: jest.fn(), + }; + + const component = shallow(); + + expect(sadState.handleSubmit.mock.calls.length).toBe(0); + component.find('form').simulate('submit'); + expect(sadState.handleSubmit.mock.calls.length).toBe(1); + }) + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/__snapshots__/Step1_LoginForm.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/__snapshots__/Step1_LoginForm.test.jsx.snap new file mode 100644 index 000000000..b747c6459 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/__tests__/__snapshots__/Step1_LoginForm.test.jsx.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/hub/Views/Step1_LoginForm component Snapshot tests with react-test-renderer Login Form view is rendered correctly 1`] = ` +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+ forgot_password +
+
+
+
+
+ +
+
+`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/index.js b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/index.js new file mode 100644 index 000000000..5b2b1af68 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step1_LogInForm/index.js @@ -0,0 +1,38 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Onboarding Login View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { buildReduxHOC } from '../../../../shared-hub/utils'; + +import Step1_LogInFormContainer from './Step1_LogInFormContainer'; +import { + login, + getUser, + getUserSettings, + resetPassword +} from '../../../../Account/AccountActions'; +import { getTheme } from '../../../../panel/actions/PanelActions'; +import setToast from '../../../../shared-hub/actions/ToastActions'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; + +const stateSlices = ['account']; +const actionCreators = { + setToast, + login, + getUser, + getUserSettings, + getTheme, + resetPassword, + setSetupStep, +}; + +export default buildReduxHOC(stateSlices, actionCreators, Step1_LogInFormContainer); diff --git a/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.jsx b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.jsx new file mode 100644 index 000000000..4b774d70b --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.jsx @@ -0,0 +1,235 @@ +/** + * Dawn Hub onboarding flow Block Settings View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Fragment, Component } from 'react'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; + +import Tooltip from '../../../../shared-components/Tooltip'; +import RadioButton from '../../../../shared-components/RadioButton/RadioButton'; +import ToggleCheckbox from '../../../../shared-components/ToggleCheckbox/ToggleCheckbox'; +import { CHOOSE_DEFAULT_SEARCH, ONBOARDING } from '../../OnboardingView/OnboardingConstants'; + +/** + * @class Implement the Block Settings View for the Dawn Hub onboarding flow + * @extends Component + * @memberof DawnHubViews + */ +class BlockSettingsView extends Component { + constructor(props) { + super(props); + this.state = { + recommendedChoices: false, + enable_ad_block: null, + kindsOfTrackers: null, + enable_anti_tracking: null, + enable_smart_block: null + }; + } + + toggleRecommendedChoices = (value) => { + if (value === true) { + this.setState({ + recommendedChoices: true, + enable_ad_block: true, + kindsOfTrackers: 2, + enable_anti_tracking: true, + enable_smart_block: true + }); + } else { + this.setState({ + recommendedChoices: false, + enable_ad_block: null, + kindsOfTrackers: null, + enable_anti_tracking: null, + enable_smart_block: null + }); + } + } + + handleAnswerChange = (category, answer) => { + this.setState({ [category]: answer }); + } + + // Refer to https://ghostery.atlassian.net/wiki/spaces/BI/pages/488079383/Ghostery+Browser+-+Onboarding+Pings for setup_number string formatting + buildSetupNumberString = () => { + const { + enable_ad_block, + kindsOfTrackers, + enable_anti_tracking, + enable_smart_block + } = this.state; + + const partOne = (enable_ad_block) ? '1' : '2'; + const partTwo = kindsOfTrackers.toString(); + const partThree = (enable_anti_tracking) ? '1' : '2'; + const partFour = (enable_smart_block) ? '1' : '2'; + + return `${partOne}${partTwo}${partThree}${partFour}`; + } + + handleSubmit = () => { + const { + enable_ad_block, kindsOfTrackers, enable_anti_tracking, enable_smart_block + } = this.state; + + const { actions } = this.props; + const { setToast } = actions; + + // Will only change user settings if all questions are answered + if (enable_ad_block !== null && kindsOfTrackers !== null && enable_anti_tracking !== null && enable_smart_block !== null) { + setToast({ + toastMessage: '', + toastClass: '' + }); + + const { + setAdBlock, setAntiTracking, setSmartBlocking, setBlockingPolicy, setSetupStep + } = actions; + const { history } = this.props; + + setAdBlock({ enable_ad_block }); + setAntiTracking({ enable_anti_tracking }); + setSmartBlocking({ enable_smart_block }); + + let blockingPolicy; + switch (kindsOfTrackers) { + case 1: + blockingPolicy = 'BLOCKING_POLICY_EVERYTHING'; + break; + case 2: + blockingPolicy = 'BLOCKING_POLICY_RECOMMENDED'; + break; + case 3: + blockingPolicy = 'BLOCKING_POLICY_NOTHING'; + break; + default: + break; + } + setBlockingPolicy({ blockingPolicy }); + + setSetupStep({ + setup_step: CHOOSE_DEFAULT_SEARCH, + dawn_setup_number: this.buildSetupNumberString(), + origin: ONBOARDING + }); + history.push('/onboarding/3'); + } else { + setToast({ + toastMessage: t('ghostery_dawn_hub_blocking_settings_view_toast_error_message'), + toastClass: 'alert' + }); + } + } + + renderAnswerBlock = (checked, category, answer, label) => ( +
this.handleAnswerChange(category, answer)}> +
+ {}} /> +
+
{label}
+
+ ); + + render() { + const { + recommendedChoices, enable_ad_block, kindsOfTrackers, enable_anti_tracking, enable_smart_block + } = this.state; + const { actions } = this.props; + const { logout } = actions; + return ( + +
+
+ + logout()}> + {t('ghostery_dawn_onboarding_back')} + +
+
+
+
{t('ghostery_dawn_onboarding_which_privacy_plan')}
+
{t('ghostery_dawn_onboarding_tell_us_your_preferences')}
+
+
+ this.toggleRecommendedChoices(!recommendedChoices)} + /> +
this.toggleRecommendedChoices(!recommendedChoices)}>{t('ghostery_dawn_onboarding_recommended_choices')}
+
+
    +
  1. {t('ghostery_dawn_onboarding_question_block_ads')}
  2. + {this.renderAnswerBlock((enable_ad_block === true), 'enable_ad_block', true, t('hub_setup_modal_button_yes'))} + {this.renderAnswerBlock((enable_ad_block === false), 'enable_ad_block', false, t('hub_setup_modal_button_no'))} +
  3. +
    + {t('ghostery_dawn_onboarding_question_kinds_of_trackers')} +
    + +
    +
    +
  4. + {this.renderAnswerBlock((kindsOfTrackers === 1), 'kindsOfTrackers', 1, t('ghostery_dawn_onboarding_kinds_of_trackers_all'))} + {this.renderAnswerBlock((kindsOfTrackers === 2), 'kindsOfTrackers', 2, t('ghostery_dawn_onboarding_kinds_of_trackers_ad_and_analytics'))} + {this.renderAnswerBlock((kindsOfTrackers === 3), 'kindsOfTrackers', 3, t('ghostery_dawn_onboarding_kinds_of_trackers_none'))} +
  5. +
    + {t('ghostery_dawn_onboarding_question_anti_tracking')} +
    + +
    +
    +
  6. + {this.renderAnswerBlock((enable_anti_tracking === true), 'enable_anti_tracking', true, t('hub_setup_modal_button_yes'))} + {this.renderAnswerBlock((enable_anti_tracking === false), 'enable_anti_tracking', false, t('hub_setup_modal_button_no'))} +
  7. +
    + {t('ghostery_dawn_onboarding_question_smart_browsing')} +
    + +
    +
    +
  8. + {this.renderAnswerBlock((enable_smart_block === true), 'enable_smart_block', true, t('hub_setup_modal_button_yes'))} + {this.renderAnswerBlock((enable_smart_block === false), 'enable_smart_block', false, t('hub_setup_modal_button_no'))} +
+
+ +
+
+ ); + } +} + +export default BlockSettingsView; + +// PropTypes ensure we pass required props of the correct type +BlockSettingsView.propTypes = { + actions: PropTypes.shape({ + logout: PropTypes.func.isRequired, + setAntiTracking: PropTypes.func.isRequired, + setAdBlock: PropTypes.func.isRequired, + setSmartBlocking: PropTypes.func.isRequired, + setBlockingPolicy: PropTypes.func.isRequired, + setToast: PropTypes.func.isRequired, + setSetupStep: PropTypes.func.isRequired, + }).isRequired, +}; diff --git a/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.scss b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.scss new file mode 100644 index 000000000..54a00adcc --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.scss @@ -0,0 +1,152 @@ +/** + * BlockSettingsView Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + + +.BlockSettingsView__container { + display: flex; + flex-direction: column; + font-size: 18px; + line-height: 2.33; + color: $tundora; + margin: 45px auto 83px auto; +} + +.BlockSettingsView__relativeContainer { + position: relative; +} + +.BlockSettingsView__caret.left { + margin: 7px auto 0 auto; + height: 10px; + width: 10px; + border-left: 2px solid $tundora; + border-top: 2px solid $tundora; + cursor: pointer; + transform: rotate(-45deg); +} + +.BlockSettingsView__backContainer { + position: absolute; + display: flex; + width: 68px; + margin-top: 60px; + @include breakpoint(xlarge down) { + margin-top: 22px; + } + .BlockSettingsView__back { + margin-top: 8px; + font-size: 16px; + color: $tundora; + text-decoration: underline; + } +} + +.BlockSettingsView__title { + font-size: 24px; + font-weight: 500; + line-height: 2.33; + text-align: center; + @include breakpoint(xxlarge down) { + max-width: 580px; + margin: auto; + } +} + +.BlockSettingsView__subtitle { + margin-bottom: 47px; + text-align: center; +} + +.BlockSettingsView_formBlock { + width: 800px; + text-align: left; +} + +.BlockSettingsView__ctaButton { + display: flex; + justify-content: center; + margin: auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; +} + +.BlockSettingsView_question { + margin-top: 30px; + margin-bottom: 15px; + font-weight: 500; +} + +.BlockSettingsView__radioButtonContainer { + padding: 11px; + display: flex; + justify-content: center; + cursor: pointer; +} + +.BlockSettingsView_answerBlock { + display: flex; + align-items: center; + cursor: pointer; +} + +.BlockSettingsView_checkboxBlock { + display: flex; + align-items: center; + font-size: 14px; +} + +.BlockSettingsView_checkbox { + width: 20px; + height: 20px; + padding: 0; + margin-left: -14px; +} + +.BlockSettingsView_checkboxLabel { + cursor: pointer; +} + +.BlockSettingsView__infoIcon { + width: 18px; + height: 25px; + background-position: center; + background-repeat: no-repeat; + background-image: url('/app/images/hub/setup/info.svg'); + margin-left: 8px; +} + +.BlockSettingsView_questionBlock { + display: flex; +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/BlockSettingsView.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/BlockSettingsView.test.jsx new file mode 100644 index 000000000..614643426 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/BlockSettingsView.test.jsx @@ -0,0 +1,86 @@ +/** + * BlockSettings View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import BlockSettingsView from '../BlockSettingsView'; + +const noop = () => {}; +jest.mock('../../../../../shared-components/Tooltip'); + +describe('app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.test.jsx', () => { + const initialState = { + actions: { + logout: noop, + setAntiTracking: noop, + setAdBlock: noop, + setSmartBlocking: noop, + setBlockingPolicy: noop, + setToast: noop, + setSetupStep: noop, + } + }; + describe('Snapshot tests with react-test-renderer', () => { + test('BlockSettings View is rendered correctly', () => { + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('BlockSettings View happy path', () => { + const happyState = { + ...initialState, + actions: { + logout: noop, + setAntiTracking: jest.fn(), + setAdBlock: jest.fn(), + setSmartBlocking: jest.fn(), + setBlockingPolicy: jest.fn(), + setToast: noop, + setSetupStep: jest.fn(), + }, + history: { + push: noop + } + }; + const component = shallow(); + + const instance = component.instance(); + + instance.toggleRecommendedChoices(true); + expect(component.state('enable_ad_block')).toBe(true); + expect(component.state('kindsOfTrackers')).toBe(2); + expect(component.state('enable_anti_tracking')).toBe(true); + expect(component.state('enable_smart_block')).toBe(true); + + instance.handleAnswerChange('enable_ad_block', false); + expect(component.state('enable_ad_block')).toBe(false); + + instance.handleSubmit(); + expect(happyState.actions.setAntiTracking.mock.calls.length).toBe(1); + expect(happyState.actions.setAdBlock.mock.calls.length).toBe(1); + expect(happyState.actions.setSmartBlocking.mock.calls.length).toBe(1); + expect(happyState.actions.setBlockingPolicy.mock.calls.length).toBe(1); + expect(happyState.actions.setSetupStep.mock.calls.length).toBe(1); + + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/__snapshots__/BlockSettingsView.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/__snapshots__/BlockSettingsView.test.jsx.snap new file mode 100644 index 000000000..afe34d766 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/__tests__/__snapshots__/BlockSettingsView.test.jsx.snap @@ -0,0 +1,336 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.test.jsx Shallow snapshot tests rendered with Enzyme BlockSettings View happy path 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.test.jsx Snapshot tests with react-test-renderer BlockSettings View is rendered correctly 1`] = ` +Array [ + , +
+
+ ghostery_dawn_onboarding_which_privacy_plan +
+
+ ghostery_dawn_onboarding_tell_us_your_preferences +
+
+
+
+ + + +
+
+ ghostery_dawn_onboarding_recommended_choices +
+
+
    +
  1. + ghostery_dawn_onboarding_question_block_ads +
  2. +
    +
    + + + + + +
    +
    + hub_setup_modal_button_yes +
    +
    +
    +
    + + + + + +
    +
    + hub_setup_modal_button_no +
    +
    +
  3. +
    + ghostery_dawn_onboarding_question_kinds_of_trackers +
    +
    +
  4. +
    +
    + + + + + +
    +
    + ghostery_dawn_onboarding_kinds_of_trackers_all +
    +
    +
    +
    + + + + + +
    +
    + ghostery_dawn_onboarding_kinds_of_trackers_ad_and_analytics +
    +
    +
    +
    + + + + + +
    +
    + ghostery_dawn_onboarding_kinds_of_trackers_none +
    +
    +
  5. +
    + ghostery_dawn_onboarding_question_anti_tracking +
    +
    +
  6. +
    +
    + + + + + +
    +
    + hub_setup_modal_button_yes +
    +
    +
    +
    + + + + + +
    +
    + hub_setup_modal_button_no +
    +
    +
  7. +
    + ghostery_dawn_onboarding_question_smart_browsing +
    +
    +
  8. +
    +
    + + + + + +
    +
    + hub_setup_modal_button_yes +
    +
    +
    +
    + + + + + +
    +
    + hub_setup_modal_button_no +
    +
    +
+
+ +
, +] +`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/index.js b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/index.js new file mode 100644 index 000000000..ec60917d4 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/index.js @@ -0,0 +1,33 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Onboarding Block Settings View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { withRouter } from 'react-router-dom'; +import BlockSettingsView from './BlockSettingsView'; +import { buildReduxHOC } from '../../../../shared-hub/utils'; +import { logout } from '../../../../Account/AccountActions'; +import { setAntiTracking, setAdBlock, setSmartBlocking } from '../../../../shared-hub/actions/AntiSuiteActions'; +import setBlockingPolicy from '../../../../shared-hub/actions/BlockingPolicyActions'; +import setToast from '../../../../shared-hub/actions/ToastActions'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; + +const actionCreators = { + logout, + setAntiTracking, + setAdBlock, + setSmartBlocking, + setBlockingPolicy, + setToast, + setSetupStep, +}; + +export default withRouter(buildReduxHOC(null, actionCreators, BlockSettingsView)); diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchActions.js b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchActions.js new file mode 100644 index 000000000..40180dec7 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchActions.js @@ -0,0 +1,18 @@ +/** + * Choose Default Search action creators for the Dawn onboarding hub + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { SET_DEFAULT_SEARCH } from './ChooseDefaultSearchConstants'; + +const setDefaultSearch = data => ({ type: SET_DEFAULT_SEARCH, data }); + +export default setDefaultSearch; diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchConstants.js b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchConstants.js new file mode 100644 index 000000000..29a6ad199 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchConstants.js @@ -0,0 +1,29 @@ +/** + * Choose Default Search constants for use by the Dawn onboarding hub + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +export const SET_DEFAULT_SEARCH = 'SET_DEFAULT_SEARCH'; +export const SEARCH_GHOSTERY = 'Ghostery'; +export const SEARCH_BING = 'Bing'; +export const SEARCH_YAHOO = 'Yahoo'; +export const SEARCH_STARTPAGE = 'StartPage'; +export const SEARCH_DUCKDUCK_GO = 'DuckDuckGo'; +export const SEARCH_ECOSIA = 'Ecosia Search'; +export const SEARCH_EKORU = 'ekoru'; +export const SEARCH_GIBIRU = 'Gibiru.com'; +export const SEARCH_GOOGLE = 'Google'; +export const SEARCH_ONESEARCH = 'OneSearch'; +export const SEARCH_PRIVADO = 'Privado'; +export const SEARCH_QWANT = 'Qwant'; +export const SEARCH_ENCRYPT = 'TODO: Add Search Encrypt browser.search.get() string'; +export const SEARCH_TAILCAT = 'Tailcat'; +export const SEARCH_OTHER = t('ghostery_dawn_onboarding_other'); diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchReducer.js b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchReducer.js new file mode 100644 index 000000000..299aaed39 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchReducer.js @@ -0,0 +1,36 @@ +/** + * Reducer for the Choose Default Search view in the Dawn onboarding hub + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { + SET_DEFAULT_SEARCH, + SEARCH_GHOSTERY +} from './ChooseDefaultSearchConstants'; + +const initialState = { + defaultSearch: SEARCH_GHOSTERY, +}; + +function ChooseDefaultSearchReducer(state = initialState, action) { + switch (action.type) { + case SET_DEFAULT_SEARCH: { + return { + ...state, + defaultSearch: action.data + }; + } + + default: return state; + } +} + +export default ChooseDefaultSearchReducer; diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.jsx b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.jsx new file mode 100644 index 000000000..7a422102c --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.jsx @@ -0,0 +1,375 @@ +/** + * Dawn Hub onboarding flow Choose Default Search View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Component, Fragment } from 'react'; +import { NavLink } from 'react-router-dom'; +import ClassNames from 'classnames'; +import { alwaysLog } from '../../../../../src/utils/common'; +import RadioButton from '../../../../shared-components/RadioButton'; +import { ONBOARDING, CHOOSE_PLAN } from '../../OnboardingView/OnboardingConstants'; +import { + SEARCH_GHOSTERY, + SEARCH_BING, + SEARCH_YAHOO, + SEARCH_STARTPAGE, + SEARCH_DUCKDUCK_GO, + SEARCH_ECOSIA, + SEARCH_EKORU, + SEARCH_GIBIRU, + SEARCH_GOOGLE, + SEARCH_ONESEARCH, + SEARCH_PRIVADO, + SEARCH_QWANT, + SEARCH_ENCRYPT, + SEARCH_TAILCAT, + SEARCH_OTHER +} from './ChooseDefaultSearchConstants'; +import { Modal } from '../../../../shared-components'; + +const searchSetupNumbers = [ + { name: SEARCH_GHOSTERY, dawn_setup_number: 1 }, + { name: SEARCH_BING, dawn_setup_number: 2 }, + { name: SEARCH_YAHOO, dawn_setup_number: 3 }, + { name: SEARCH_STARTPAGE, dawn_setup_number: 4 }, + { name: SEARCH_GOOGLE, dawn_setup_number: 5 }, + { name: SEARCH_DUCKDUCK_GO, dawn_setup_number: 6 }, + { name: SEARCH_ECOSIA, dawn_setup_number: 7 }, + { name: SEARCH_EKORU, dawn_setup_number: 8 }, + { name: SEARCH_GIBIRU, dawn_setup_number: 9 }, + { name: SEARCH_ONESEARCH, dawn_setup_number: 10 }, + { name: SEARCH_PRIVADO, dawn_setup_number: 11 }, + { name: SEARCH_QWANT, dawn_setup_number: 12 }, + { name: SEARCH_ENCRYPT, dawn_setup_number: 13 }, + { name: SEARCH_TAILCAT, dawn_setup_number: 14 }, +]; + +const GLOW_BROWSER_SEARCH_GET_NAME = 'Ghostery Glow'; +const STARTPAGE_BROWSER_SEARCH_GET_NAME = 'StartPage'; + +class ChooseDefaultSearchView extends Component { + constructor(props) { + super(props); + + this.state = { + chosenSearch: SEARCH_GHOSTERY, + searchBeingConsidered: null, + otherSearchSelected: null, + otherListOpen: false, + modalActive: false, + otherSearchOptionsFetched: false, + otherSearchOptions: [], + }; + + this.fetchSearchEnginesAsync = this.fetchSearchEnginesAsync.bind(this); + } + + componentDidMount() { + document.addEventListener('click', this.handleClickAway); + + this.fetchSearchEnginesAsync(); + } + + componentWillUnmount() { + document.removeEventListener('click', this.handleClickAway); + } + + async fetchSearchEnginesAsync() { + // eslint-disable-next-line no-undef + if (typeof browser === 'undefined' || typeof browser.search === 'undefined') { // we are not in Dawn + this.setState(state => ({ + ...state, + otherSearchOptionsFetched: true, + })); + return; + } + // eslint-disable-next-line no-undef + const response = await browser.search.get(); // we are in Dawn, where this API is supported (unlike Chrome), and where the necessary search permission is added at build time (unlike non-Dawn builds of GBE) + + // a successful response is guaranteed to be an array of search engine objects + // see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/search/get + const otherOptions = response + .map(item => item.name) + // Two versions of the Startpage identifier are included for safety as it's unclear which version is / will be used + .filter(name => ![SEARCH_YAHOO, GLOW_BROWSER_SEARCH_GET_NAME, STARTPAGE_BROWSER_SEARCH_GET_NAME, SEARCH_STARTPAGE, SEARCH_BING].includes(name)); + + this.setState(state => ({ + ...state, + otherSearchOptionsFetched: true, + otherSearchOptions: otherOptions + })); + } + + updateSelection = () => this.setState(prevState => ( + { + chosenSearch: prevState.searchBeingConsidered, + searchBeingConsidered: null, + modalActive: false + } + )); + + cancelSelection = () => this.setState({ modalActive: false, searchBeingConsidered: null }); + + updateOtherSearchSelection = otherSelected => this.setState({ otherSearchSelected: otherSelected }); + + updateOtherListOpen = open => this.setState({ otherListOpen: open }); + + triggerConfirmationModal = selection => this.setState({ modalActive: true, searchBeingConsidered: selection }); + + handleClickAway = (e) => { + const { otherListOpen } = this.state; + + const closeDropdownOnClickAway = (open, key, ref) => { + if (open && !ref.contains(e.target)) { + this.setState({ [`${key}`]: false }); + } + }; + closeDropdownOnClickAway(otherListOpen, 'otherListOpen', this.otherListRef); + } + + handleSubmit = () => { + const { chosenSearch, otherSearchSelected } = this.state; + const { actions, history } = this.props; + const { setSetupStep, setDefaultSearch } = actions; + + const chosenSearchName = chosenSearch === SEARCH_OTHER + ? otherSearchSelected + : chosenSearch; + + const payload = { + type: 'setDefaultSearch', + search: chosenSearchName, + isOther: chosenSearch === SEARCH_OTHER, + }; + + // The try/catch wrapper facilitates testing in non-Dawn browsers which have no search@ghostery.com extension + try { + chrome.runtime.sendMessage('search@ghostery.com', payload, () => { + }); + } catch (error) { + alwaysLog(['Ilya: If you are seeing this error in Dawn, please report it. In other browsers, it is expected', error]); + } + + setDefaultSearch(chosenSearchName); + + setSetupStep({ + setup_step: CHOOSE_PLAN, + dawn_setup_number: searchSetupNumbers.find(elem => elem.name === chosenSearchName).dawn_setup_number, + origin: ONBOARDING + }); + history.push(`/${ONBOARDING}/${CHOOSE_PLAN}`); + } + + renderGhosteryOptionDescription = () => ( + +
{t('ghostery_dawn_onboarding_ad_free_private_search')}
+
{t('ghostery_dawn_onboarding_recommended')}
+
+ ); + + renderStartpageOptionDescription = () => ( +
{t('ghostery_dawn_onboarding_ad_supported_private_search')}
+ ); + + renderOtherOptionDescription = () => { + const { otherSearchSelected, otherListOpen } = this.state; + const dropdownOpen = otherListOpen ? 'expanded' : ''; + return ( + +
+
{SEARCH_OTHER}
+
{t('ghostery_dawn_onboarding_choose_alternate_search')}
+
+
{ this.otherListRef = node; }} + className={`ChooseSearchView__optionDropdownContainer ${dropdownOpen}`} + onClick={() => this.updateOtherListOpen(!otherListOpen)} + > +
+
+ {otherSearchSelected || t('ghostery_dawn_onboarding_select_option')} + +
+ {otherListOpen && this.renderOtherOptionsList()} +
+
+
+ ); + } + + renderOtherOptionsList = () => { + const { otherSearchOptions } = this.state; + + return ( + + {otherSearchOptions.map(otherSearchOption => ( +
{ + this.updateOtherSearchSelection(otherSearchOption); + this.triggerConfirmationModal(SEARCH_OTHER); + }} + > + {otherSearchOption} +
+ ))} +
+ ); + } + + renderOptionContainer = (chosenSearch, optionName) => { + const selected = (chosenSearch === optionName); + const containerClasses = ClassNames('ChooseSearchView__optionContainer', { selected }); + const logoFilename = `/app/images/hub/ChooseDefaultSearchView/search-engine-logo-${optionName.toLocaleLowerCase()}.svg`; + + return ( +
{ + if (optionName !== SEARCH_OTHER) { + this.triggerConfirmationModal(optionName); + } + }} + className={containerClasses} + > +
+ {(optionName !== SEARCH_OTHER || (optionName === SEARCH_OTHER && selected)) && ( + {}} + altDesign + /> + ) + } +
+
+ {(optionName !== SEARCH_OTHER) && ( + + )} + {(optionName === SEARCH_GHOSTERY) && this.renderGhosteryOptionDescription()} + {(optionName === SEARCH_STARTPAGE) && this.renderStartpageOptionDescription()} + {(optionName === SEARCH_OTHER) && this.renderOtherOptionDescription()} +
+
+ ); + } + + renderConfirmationModal = () => { + const { searchBeingConsidered, otherSearchSelected } = this.state; + const logoFilename = `/app/images/hub/ChooseDefaultSearchView/search-engine-logo-${searchBeingConsidered.toLocaleLowerCase()}.svg`; + + return ( + +
+ +
+ {searchBeingConsidered === SEARCH_OTHER ? ( +
+ {SEARCH_OTHER} +
+ ) : + + } +
+ {searchBeingConsidered === SEARCH_STARTPAGE && t('ghostery_dawn_onboarding_startpage_warning')} + {searchBeingConsidered === SEARCH_BING && t('ghostery_dawn_onboarding_bing_warning')} + {searchBeingConsidered === SEARCH_YAHOO && t('ghostery_dawn_onboarding_yahoo_warning')} + {searchBeingConsidered === SEARCH_GHOSTERY && t('ghostery_dawn_onboarding_glow_benefit')} + {(searchBeingConsidered === SEARCH_OTHER) && ( + + { + `${t('ghostery_dawn_onboarding_you_have_selected_an_alternate_search_engine')} \n + ${otherSearchSelected}` + } + + )} +
+
+ +
+ +
+
+ +
+ + ); + } + + renderSearchOptions = () => { + const { chosenSearch, otherSearchOptions } = this.state; + + // No sense showing dropdown if there are no other options + const showOtherOptionsDropdown = otherSearchOptions.length > 0; + + return ( + +
+
+ + + {t('ghostery_dawn_onboarding_back')} + +
+
+
+
{t('choose_your_default_search')}
+
{t('pick_a_default_search_engine')}
+
+ {this.renderOptionContainer(chosenSearch, SEARCH_GHOSTERY)} + {this.renderOptionContainer(chosenSearch, SEARCH_STARTPAGE)} + {this.renderOptionContainer(chosenSearch, SEARCH_BING)} + {showOtherOptionsDropdown && this.renderOptionContainer(chosenSearch, SEARCH_OTHER)} + {this.renderOptionContainer(chosenSearch, SEARCH_YAHOO)} +
+ +
+
+ ); + } + + render() { + const { modalActive, otherSearchOptionsFetched } = this.state; + + if (!otherSearchOptionsFetched) return null; + + return ( +
+ {modalActive && this.renderConfirmationModal()} + {!modalActive && this.renderSearchOptions()} +
+ ); + } +} + +export default ChooseDefaultSearchView; diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.scss b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.scss new file mode 100644 index 000000000..d46a498ee --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.scss @@ -0,0 +1,325 @@ +.ChooseSearchView__container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + font-size: 18px; + line-height: 2.33; + color: $tundora; + margin: 45px auto 83px auto; +} + +.ChooseSearchView__relativeContainer { + position: relative; + @include breakpoint(small down) { + margin-left: 0; + } +} +.ChooseSearchView__caret.left { + margin: 7px auto 0 auto; + height: 10px; + width: 10px; + border-left: 2px solid $tundora; + border-top: 2px solid $tundora; + cursor: pointer; + transform: rotate(-45deg); +} + +.ChooseSearchView__backContainer { + position: absolute; + display: flex; + width: 68px; + margin-top: 15px; + @include breakpoint(small down) { + margin-top: -21px; + } + .ChooseSearchView__back { + margin-top: 8px; + font-size: 16px; + color: $tundora; + text-decoration: underline; + } +} + + +.ChooseSearchView__title { + font-size: 24px; + font-weight: 500; + line-height: 2.33; + text-align: center; + @include breakpoint(xxlarge down) { + max-width: 400px; + margin: auto; + } +} + +.ChooseSearchView__subtitle { + margin-bottom: 47px; + text-align: center; +} + +.ChooseSearchView__optionsContainer { + display: grid; + grid-template-columns: 1fr 1fr; +} + + +.ChooseSearchView__optionContainer { + display: flex; + width: 354px; + height: 125px; + margin: 20px; + border: solid 1px $tundora; + border-radius: 4px; + cursor: pointer; + + &.selected { + border: solid 4px $ghosty-blue; + + & .ChooseSearchView__optionDropdownContainer { + top: 7px; + } + } + + &:hover { + border: solid 4px $ghosty-blue; + + & .ChooseSearchView__optionDropdownContainer { + top: 7px; + } + } + + & .ChooseSearchView__optionDropdownContainer { + top: 10px; + } + +} + +.ChooseSearchView__optionRadioButtonContainer { + display: flex; + align-items: center; + justify-content: center; + width: 25%; +} + +.ChooseSearchView__optionDescriptionContainer { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 75%; + padding-right: 15%; +} + +.ChooseSearchView__optionDescriptionHeader { + position: absolute; + margin-top: -45px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +@mixin option-font { + line-height: 0.8; + font-size: 16px; + font-weight: 500; +} +.ChooseSearchView__optionTitle { + @include option-font; +} + +.ChooseSearchView__optionDescriptionTitle { + padding-top: 10px; + line-height: 0.8; + font-size: 14px; + font-weight: 500; +} + +.ChooseSearchView__optionDescriptionSubtitle { + font-size: 14px; +} + +.ChooseSearchView__optionDropdownContainer { + position: absolute; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + margin-top: 50px; + padding-bottom: 5px; + width: 184px; + border-radius: 4px; + border: solid 2px #4a4a4a; + background-color: rgba(255, 255, 255, 0.95); + + &:hover { + border-color: #00aef0 + } +} + +.ChooseSearchView__optionDropdown { + position: relative; + top: 12px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.ChooseSearchView__optionDropdownCaret { + position: relative; + left: 18px; + &.expanded { + transform: rotate(180deg); + } +} + +.ChooseSearchView__optionDropdownItem { + @include option-font; + margin-bottom: 16px; + &:hover { + color: #00aef0; + text-decoration: underline; + } +} + +.ChooseSearchView__caret { + height: 35px; + width: 25px; + padding-top: 9px; + padding-right: 0; + background-repeat: no-repeat; + background-position: center center; + background-size: 9px 5px; + background-image: buildIconCaretDown(#4a4a4a); +} + +.ChooseSearchView__nextButton { + display: flex; + justify-content: center; + height: 44px; + width: 162px; + cursor: pointer; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; +} + +.ChooseSearchView__modalCancelButton { + display: flex; + justify-content: center; + height: 44px; + width: 162px; + cursor: pointer; + padding: 7.7px 14px; + line-height: 22px; + border: solid 2px $tundora; + &:hover { + border: none; + background: $ghosty-blue; + color: $white; + } + color: $tundora; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + line-height: 2.05; + cursor: pointer; +} + +.ChooseSearchView__modalConfirmButton { + display: flex; + justify-content: center; + height: 44px; + width: 162px; + cursor: pointer; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; +} + +.ChooseSearchView__modalButtonsContainer { + display: flex; + flex-direction: row; +} + +.ChooseSearchView__modalButtonDivider { + width: 24px; +} + +.ChooseSearchView__modalOptionLogo { + padding: 70px 0; +} + +.ChooseSearchView__modalDescription { + font-size: 24px; + font-weight: 500; + line-height: 1.75; + text-align: center; + color: $tundora; + width: 70%; + margin-bottom: 113px; + white-space: pre-line; +} + +.ChooseSearchView__modalContent { + position: relative; + width: 901px; + min-height: 632px; + padding: 20px; + background-color: $white; + border: 1px solid $tundora; + z-index: 10; +} + +.ChooseSearchView__modalMain { + display: flex; + flex-direction: column; + align-items: center; +} + +.ChooseSearchView__modalHeader { + font-size: 48px; + font-weight: 500; + margin-bottom: 110px; +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/index.js b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/index.js new file mode 100644 index 000000000..bd33072be --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/index.js @@ -0,0 +1,25 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Choose Default Search View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { withRouter } from 'react-router-dom'; +import ChooseDefaultSearchView from './ChooseDefaultSearchView'; +import { buildReduxHOC } from '../../../../shared-hub/utils'; +import setDefaultSearch from './ChooseDefaultSearchActions'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; + +const actionCreators = { + setDefaultSearch, + setSetupStep, +}; + +export default withRouter(buildReduxHOC(null, actionCreators, ChooseDefaultSearchView)); diff --git a/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.jsx b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.jsx new file mode 100644 index 000000000..90b569e3c --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.jsx @@ -0,0 +1,413 @@ +/** + * Dawn Hub onboarding flow Choose Plan View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Fragment } from 'react'; +import { NavLink } from 'react-router-dom'; +import ClassNames from 'classnames'; +import PropTypes from 'prop-types'; +import RadioButton from '../../../../shared-components/RadioButton'; +import globals from '../../../../../src/classes/Globals'; +import { BASIC, PLUS, PREMIUM } from '../../../../hub/Views/UpgradePlanView/UpgradePlanViewConstants'; +import { + CHOOSE_PLAN, + ONBOARDING, + FREE_USER_NO_TRIAL, + FREE_USER_PLUS_TRIAL, + FREE_USER_PLUS_SUBSCRIPTION, + FREE_USER_PREMIUM_SUBSCRIPTION, + PLUS_SUBSCRIBER_KEEP_SUBSCRIPTION, + PLUS_SUBSCRIBER_PREMIUM_SUBSCRIPTION, + PREMIUM_SUBSCRIBER_KEEP_SUBSCRIPTION +} from '../../OnboardingView/OnboardingConstants'; +import { SEARCH_GHOSTERY } from '../Step3_ChooseDefaultSearchView/ChooseDefaultSearchConstants'; + +const glowFreeTrialLink = `${globals.GLOWSTERY_BASE_URL}/account?utm_source=dawn&utm_medium=introhub&utm_campaign=onboarding`; +const premiumCheckoutLink = `${globals.CHECKOUT_BASE_URL}/premium?utm_source=dawn&utm_medium=introhub&utm_campaign=onboarding`; + +const searchPromo = () => ( +
+
+
{ t('ghostery_dawn_onboarding_ad_free_with_ghostery_plus_subscription') }
+
+ { `(${t('ghostery_dawn_onboarding_ad_free_promo')})` } +
+
{ t('ghostery_dawn_onboarding_ad_free_promo_description') }
+
+); + +const basicCard = (checked, handleClick) => { + const cardClassNames = ClassNames('ChoosePlanView__card basic', { + checked + }); + return ( +
+
+
+ +
+
+
+
+
+

Ghostery

+
+

{t('hub_upgrade_plan_free')}

+
+

{t('hub_upgrade_basic_protection')}

+
+
+ + {t('ghostery_dawn_onboarding_private_search')} +
+
+ + {t('ghostery_dawn_onboarding_tracker_protection')} +
+
+ + {t('ghostery_dawn_onboarding_speedy_page_loads')} +
+
+ + {t('ghostery_dawn_onboarding_intelligence_technology')} +
+
+
+
+ ); +}; + +class ChoosePlanView extends React.Component { + constructor(props) { + super(props); + this.state = { + selectedPlan: '', + expanded: false + }; + // User object doesn't get populated immediately, let's delay the first render + setTimeout(this.setDefaultPlan, 200); + } + + setDefaultPlan = () => { + const { user } = this.props; + const isPlus = (user && user.plusAccess) || false; + const isPremium = (user && user.premiumAccess) || false; + + if (isPremium) { + this.selectPremiumPlan(); + return; + } + if (isPlus) { + this.selectPlusPlan(); + return; + } + this.selectBasicPlan(); + } + + isBasicPlanChecked = () => { + const { selectedPlan } = this.state; + return (selectedPlan === BASIC); + }; + + isPlusPlanChecked = () => { + const { selectedPlan } = this.state; + return (selectedPlan === PLUS); + }; + + isPremiumPlanChecked = () => { + const { selectedPlan } = this.state; + return (selectedPlan === PREMIUM); + }; + + selectBasicPlan = () => this.setState({ selectedPlan: BASIC }); + + selectPlusPlan = () => this.setState({ selectedPlan: PLUS }); + + selectPremiumPlan = () => this.setState({ selectedPlan: PREMIUM }); + + toggleSection = () => { + const { expanded } = this.state; + if (expanded) { + this.setState({ expanded: !expanded }); + } else { + this.setState({ expanded: !expanded }); + } + }; + + renderTitleText = () => { + const { user } = this.props; + const isPlus = (user && user.plusAccess) || false; + const isPremium = (user && user.premiumAccess) || false; + + if (isPremium) return t('ghostery_dawn_onboarding_already_premium_subscriber'); + if (isPlus) return t('ghostery_dawn_onboarding_already_plus_subscriber'); + return t('ghostery_dawn_onboarding_your_privacy_plan'); + }; + + renderSubtitleText = (selectedGhosteryGlow) => { + const { user } = this.props; + const isPlus = (user && user.plusAccess) || false; + const isPremium = (user && user.premiumAccess) || false; + + if (selectedGhosteryGlow) return t('ghostery_dawn_onboarding_based_on_your_privacy_preferences'); + if (isPremium) return ''; + if (isPlus) return t('ghostery_dawn_onboarding_keep_your_current_plan_or_upgrade'); + return t('ghostery_dawn_onboarding_choose_an_option'); + }; + + setSetupStepAndMoveToSuccessView = (dawn_setup_number) => { + const { actions, history } = this.props; + const { setSetupStep } = actions; + setSetupStep({ setup_step: CHOOSE_PLAN, dawn_setup_number, origin: ONBOARDING }); + history.push('/onboarding/5'); + } + + plusCard = (checked, handleClick, showCTAButton = false) => { + const { actions } = this.props; + const { setSetupStep } = actions; + const cardClassNames = ClassNames('ChoosePlanView__card plus', { + checked + }); + return ( + +
+
+
+ +
+
+
+
+

Ghostery Plus

+
+ +

$4.99

+

{t('per_month')}

+
+
+

{t('hub_upgrade_additional_protection')}

+
+
+ + {t('ghostery_dawn_onboarding_private_search')} +
+
+ + {t('ghostery_dawn_onboarding_tracker_protection')} +
+
+ + {t('ghostery_dawn_onboarding_speedy_page_loads')} +
+
+ + {t('ghostery_dawn_onboarding_intelligence_technology')} +
+
+ + {t('ghostery_dawn_onboarding_ad_free')} +
+
+ + {t('ghostery_dawn_onboarding_supports_ghosterys_mission')} +
+
+
+
+ {showCTAButton && ( + setSetupStep({ setup_step: CHOOSE_PLAN, dawn_setup_number: PLUS_SUBSCRIBER_KEEP_SUBSCRIPTION, origin: ONBOARDING })}> + {t('ghostery_dawn_onboarding_keep')} + + )} + + ); + }; + + premiumCard = (checked, handleClick, showCTAButton = false) => { + const cardClassNames = ClassNames('ChoosePlanView__card premium', { + checked + }); + return ( + +
+
+
+ +
+
+
+
+
+

Ghostery Premium

+
+ +

$11.99

+

{t('per_month')}

+
+
+

{t('hub_upgrade_maximum_protection')}

+
+
+ + {t('ghostery_dawn_onboarding_private_search')} +
+
+ + {t('ghostery_dawn_onboarding_tracker_protection')} +
+
+ + {t('ghostery_dawn_onboarding_speedy_page_loads')} +
+
+ + {t('ghostery_dawn_onboarding_intelligence_technology')} +
+
+ + {t('ghostery_dawn_onboarding_ad_free')} +
+
+ + {t('ghostery_dawn_onboarding_supports_ghosterys_mission')} +
+
+ + VPN +
+
+ + {t('ghostery_dawn_onboarding_unlimited_bandwidth')} +
+
+
+
+ {showCTAButton && ( + this.setSetupStepAndMoveToSuccessView(PLUS_SUBSCRIBER_PREMIUM_SUBSCRIPTION)}>{t('ghostery_dawn_onboarding_upgrade')} + )} + + ); + }; + + render() { + const { + actions, + defaultSearch, + user, + } = this.props; + const { setSetupStep } = actions; + const { expanded, selectedPlan } = this.state; + + const isBasic = !user || (user && !user.plusAccess && !user.premiumAccess); + const isPlus = (user && user.plusAccess && !user.premiumAccess) || false; + const isPremium = (user && user.premiumAccess) || false; + + const arrowClassNames = ClassNames('ChoosePlanView__arrow', { + up: !expanded, + down: expanded + }); + + const selectedGhosteryGlow = (defaultSearch === SEARCH_GHOSTERY); + + return ( +
+
+
+ + + {t('ghostery_dawn_onboarding_back')} + +
+
+
+
{this.renderTitleText()}
+
{this.renderSubtitleText(selectedGhosteryGlow)}
+ {selectedGhosteryGlow && isBasic && ( + + {searchPromo()} + this.setSetupStepAndMoveToSuccessView(FREE_USER_PLUS_TRIAL)}>{t('ghostery_dawn_onboarding_start_trial')} +
{t('ghostery_dawn_onboarding_see_all_plans')}
+
+ + )} + {((isBasic && !selectedGhosteryGlow) || expanded || isPlus || isPremium) && ( +
+ {(isPlus) ? ( +
+
+ {this.plusCard(this.isPlusPlanChecked(), this.selectPlusPlan, isPlus)} +
+
{t('ghostery_dawn_onboarding_or')}
+
+ {this.premiumCard(this.isPremiumPlanChecked(), this.selectPremiumPlan, isPlus)} +
+
+ ) : ( +
+ {isBasic && ( + basicCard(this.isBasicPlanChecked(), this.selectBasicPlan) + )} + {!isPremium && ( + + {this.plusCard(this.isPlusPlanChecked(), this.selectPlusPlan)} + + )} + {this.premiumCard(this.isPremiumPlanChecked(), this.selectPremiumPlan)} +
+ )} + {(isBasic && ( +
+ {(selectedPlan === BASIC) && ( + setSetupStep({ setup_step: CHOOSE_PLAN, dawn_setup_number: FREE_USER_NO_TRIAL, origin: ONBOARDING })}> + {t('next')} + + )} + {selectedPlan === PLUS && ( + this.setSetupStepAndMoveToSuccessView(FREE_USER_PLUS_SUBSCRIPTION)} href={glowFreeTrialLink} target="_blank" rel="noreferrer">{t('next')} + )} + {selectedPlan === PREMIUM && ( + this.setSetupStepAndMoveToSuccessView(FREE_USER_PREMIUM_SUBSCRIPTION)} href={premiumCheckoutLink} target="_blank" rel="noreferrer">{t('next')} + )} +
+ ))} + {isPremium && ( + setSetupStep({ setup_step: CHOOSE_PLAN, dawn_setup_number: PREMIUM_SUBSCRIBER_KEEP_SUBSCRIPTION, origin: ONBOARDING })}> + {t('next')} + + )} +
+ )} +
+
+ ); + } +} + +// PropTypes ensure we pass required props of the correct type +ChoosePlanView.propTypes = { + user: PropTypes.shape({ + plusAccess: PropTypes.bool, + premiumAccess: PropTypes.bool, + }), + defaultSearch: PropTypes.string.isRequired, +}; + +// Default props used in the Plus View +ChoosePlanView.defaultProps = { + user: { + plusAccess: false, + premiumAccess: false, + }, +}; + +export default ChoosePlanView; diff --git a/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.scss b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.scss new file mode 100644 index 000000000..f077b06e9 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.scss @@ -0,0 +1,363 @@ +/** + * WelcomeView Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +$medium-large-breakpoint: 1118px; // Break when 3 cards on the screen overflow to next line + +.ChoosePlanView { + margin: 45px auto 0 auto; + padding-bottom: 20px; +} +.ChoosePlanView__relativeContainer { + width: 724px; + margin: auto; + position: relative; +} +.ChoosePlanView__caret.left { + margin: 7px auto 0 auto; + height: 10px; + width: 10px; + border-left: 2px solid $tundora; + border-top: 2px solid $tundora; + cursor: pointer; + transform: rotate(-45deg); +} +.ChoosePlanView__backContainer { + position: absolute; + display: flex; + width: 68px; + margin-top: 7px; + .ChoosePlanView__back { + margin-top: 8px; + font-size: 16px; + color: $tundora; + text-decoration: underline; + } +} +.ChoosePlanView__yourPrivacyPlan { + margin: auto; + font-size: 24px; + display: flex; + justify-content: center; + font-weight: 500; +} +.ChoosePlanView__subtitle { + margin-top: 12px; + display: flex; + justify-content: center; + font-size: 18px; +} +.ChoosePlanView__searchPromoContainer { + width: 354px; + height: 381px; + margin: 48px auto 0 auto; + border-radius: 4px; + border: solid 4px $ghosty-blue; +} +.ChoosePlanView__searchLogo { + height: 36px; + width: 166px; + margin: 65px auto 0 auto; + background-image: url('/app/images/hub/ChooseDefaultSearchView/search-engine-logo-ghostery.svg'); +} +.ChoosePlanView__adFree { + margin: 16px auto 0 auto; + color: #b8860b; + font-size: 18px; + line-height: 32px; + font-weight: 600; + width: 220px; + text-align: center; +} +.ChoosePlanView__adFreePromo { + margin: auto; + font-size: 18px; + line-height: 32px; + text-align: center; +} +.ChoosePlanView__adFreePromoDescription { + margin: 16px auto 0 auto; + height: 88px; + width: 250px; + font-size: 16px; + line-height: 1.38; + text-align: center; + color: $tundora; +} +.ChoosePlanView__searchCTAButton { + display: flex; + justify-content: center; + margin: 48px auto 0 auto; + height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + $ghosty-blue + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + color: $white; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; + + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + color: $white; + } + &:focus { + color: $white; + } +} +.ChoosePlanView__premiumCTAButton { + display: flex; + justify-content: center; + margin: 48px auto 0 auto; + height: 44px; + width: 162px; + 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; + color: $white; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; + + &:hover { + color: $white; + background-position: 0% 50%; + transition: 0.25s all; + } +} +.ChoosePlanView__seeAllPlans { + margin-top: 48px; + display: flex; + justify-content: center; + color: $ghosty-blue; + font-size: 16px; + text-decoration: underline; + cursor: pointer; +} +.ChoosePlanView__plansContainer { + margin-top: 20px; + padding-top: 20px; + + .ChoosePlanView__cardOuter { + display: flex; + flex-direction: column; + justify-content: center; + margin: 0 15px; + min-height: 670px; + } +} +.ChoosePlanView__orCardContainer { + display: flex; +} +.ChoosePlanView__or { + display: flex; + align-items: center; + justify-content: center; + padding: 0 66px; + height: 180px; + font-size: 24px; + color: $tundora; +} +.ChoosePlanView__ctaButtonContainer { + display: flex; + justify-content: center; +} +.ChoosePlanView__keepOrUpgradeContainer { + margin: 40px auto; +} +.ChoosePlanView__arrow { + margin: 15px auto 0 auto; + height: 12px; + width: 12px; + border-left: 2px solid $ghosty-blue; + border-top: 2px solid $ghosty-blue; + cursor: pointer; + + &.down { + transform: rotate(45deg); + } + &.up { + transform: rotate(225deg); + } +} +.ChoosePlanView__card { + height: 100%; + 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-bottom: rem-calc(40); + border: 4px solid white; + + &:hover, &.checked { + border: 4px solid $ghosty-blue; + .ghostery-free-image-container, + .ghostery-plus-image-container, + .ghostery-premium-image-background { + width: 244px; + margin-left: -1px; + } + } + 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); + } + .ghostery-free-image-container { + margin: -3px 0 0 -4px; + width: 250px; + height: 85px; + background-color: rgba(0, 174, 240, .25); + display: flex; + justify-content: center; + align-items: center; + + .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: -3px 0 0 -4px; + width: 250px; + height: 85px; + background-image: linear-gradient(to right, rgba(240,174,133,.25), rgba(241,216,158,.25)); + .ghostery-plus-image { + margin: auto; + width: 225px; + height: 87px; + background-image: url('/app/images/hub/upgrade/ghostery-plus-card.svg') + } + } + .ghostery-premium-image-container { + margin-left: -4px; + 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: -3px 0 0 -4px; + 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)); + } + .card-sub-header { + font-size: 14px; + } +} +.ChoosePlanView__price { + min-height: 85px; + margin-bottom: rem-calc(20); + font-size: 36px; + &-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-size: 12px; + font-weight: 500; + } + } +} +.ChoosePlanView__valuePropList { + margin: auto; + width: 174px; +} +.ChoosePlanView__cardSubCopy { + display: flex; + justify-content: flex-start; + margin-bottom: 5px; + text-align: left; +} +.check { + display: inline-block; + width: 20px; + height: 20px; + background-repeat: no-repeat; + margin-top: 3px; + padding-right: 20px; + &.blue { + background-image: buildCheckIcon($price-blue); + } + &.yellow { + background-image: buildCheckIcon($price-gold); + } + &.purple { + background-image: buildCheckIcon($price-purple); + } +} +.ChoosePlanView__radioButtonContainer { + padding: 11px; + margin-bottom: 84px; + display: flex; + justify-content: center; +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/ChoosePlanView.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/ChoosePlanView.test.jsx new file mode 100644 index 000000000..8f815686f --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/ChoosePlanView.test.jsx @@ -0,0 +1,101 @@ +/** + * ChoosePlanView Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import ChoosePlanView from '../ChoosePlanView'; + +const noop = () => {}; + +describe('app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx', () => { + const initialState = { + user: null, + selectedGhosteryGlow: true, + actions: { + setSetupStep: noop + } + }; + describe('Snapshot tests with react-test-renderer', () => { + test('ChoosePlanView is rendered correctly', () => { + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('ChoosePlanView View with user not logged in', () => { + const component = shallow(); + + const instance = component.instance(); + + instance.selectBasicPlan(); + expect(component.state('selectedPlan')).toBe('BASIC'); + + instance.selectPlusPlan(); + expect(component.state('selectedPlan')).toBe('PLUS'); + + instance.selectPremiumPlan(); + expect(component.state('selectedPlan')).toBe('PREMIUM'); + + expect(component).toMatchSnapshot(); + }); + + test('ChoosePlanView View with basic user logged in', () => { + const basicUserState = { + ...initialState, + user: { + plusAccess: false, + premiumAccess: false + } + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + test('ChoosePlanView View with plus user logged in', () => { + const plusUserState = { + ...initialState, + user: { + plusAccess: true, + premiumAccess: false + }, + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + test('ChoosePlanView View with premium user logged in', () => { + const premiumUserState = { + ...initialState, + user: { + plusAccess: true, + premiumAccess: true + } + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/__snapshots__/ChoosePlanView.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/__snapshots__/ChoosePlanView.test.jsx.snap new file mode 100644 index 000000000..17346e643 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/__tests__/__snapshots__/ChoosePlanView.test.jsx.snap @@ -0,0 +1,387 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx Shallow snapshot tests rendered with Enzyme ChoosePlanView View with basic user logged in 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx Shallow snapshot tests rendered with Enzyme ChoosePlanView View with plus user logged in 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx Shallow snapshot tests rendered with Enzyme ChoosePlanView View with premium user logged in 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx Shallow snapshot tests rendered with Enzyme ChoosePlanView View with user not logged in 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.test.jsx Snapshot tests with react-test-renderer ChoosePlanView is rendered correctly 1`] = ` +
+ +
+
+ ghostery_dawn_onboarding_your_privacy_plan +
+
+ ghostery_dawn_onboarding_choose_an_option +
+
+
+
+
+
+ + + + + +
+
+
+
+
+

+ Ghostery +

+
+

+ hub_upgrade_plan_free +

+
+

+ + hub_upgrade_basic_protection + +

+
+
+ + ghostery_dawn_onboarding_private_search +
+
+ + ghostery_dawn_onboarding_tracker_protection +
+
+ + ghostery_dawn_onboarding_speedy_page_loads +
+
+ + ghostery_dawn_onboarding_intelligence_technology +
+
+
+
+
+
+
+ + + + + +
+
+
+
+

+ Ghostery Plus +

+
+

+ $4.99 +

+

+ per_month +

+
+

+ + hub_upgrade_additional_protection + +

+
+
+ + ghostery_dawn_onboarding_private_search +
+
+ + ghostery_dawn_onboarding_tracker_protection +
+
+ + ghostery_dawn_onboarding_speedy_page_loads +
+
+ + ghostery_dawn_onboarding_intelligence_technology +
+
+ + ghostery_dawn_onboarding_ad_free +
+
+ + ghostery_dawn_onboarding_supports_ghosterys_mission +
+
+
+
+
+
+
+ + + + + +
+
+
+
+
+

+ Ghostery Premium +

+
+

+ $11.99 +

+

+ per_month +

+
+

+ + hub_upgrade_maximum_protection + +

+
+
+ + ghostery_dawn_onboarding_private_search +
+
+ + ghostery_dawn_onboarding_tracker_protection +
+
+ + ghostery_dawn_onboarding_speedy_page_loads +
+
+ + ghostery_dawn_onboarding_intelligence_technology +
+
+ + ghostery_dawn_onboarding_ad_free +
+
+ + ghostery_dawn_onboarding_supports_ghosterys_mission +
+
+ + VPN +
+
+ + ghostery_dawn_onboarding_unlimited_bandwidth +
+
+
+
+
+
+
+
+
+`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/index.jsx b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/index.jsx new file mode 100644 index 000000000..aae3575df --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/index.jsx @@ -0,0 +1,23 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Choose Plan View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 { withRouter } from 'react-router-dom'; +import { buildReduxHOC } from '../../../../shared-hub/utils'; +import ChoosePlanView from './ChoosePlanView'; +import { setSetupStep } from '../../../../shared-hub/actions/SetupLifecycleActions'; + +const actionCreators = { + setSetupStep, +}; + +export default withRouter(buildReduxHOC(['account', 'defaultSearch'], actionCreators, ChoosePlanView)); diff --git a/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.jsx b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.jsx new file mode 100644 index 000000000..94a85e6d9 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.jsx @@ -0,0 +1,52 @@ +/** + * Dawn Hub onboarding flow Success View Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Fragment } from 'react'; +import { NavLink } from 'react-router-dom'; + +/** + * A Functional React component for rendering the Browser Success View + * @return {JSX} JSX for rendering the Browser Success View of the Dawn Hub app + * @memberof DawnHubViews + */ +const SuccessView = (props) => { + const { actions } = props; + const { sendPing } = actions; + + const handleCTAButtonClick = () => { + sendPing({ type: 'gb_onboarding_success' }); + window.close(); + }; + + return ( + +
+
+ + + {t('ghostery_dawn_onboarding_back')} + +
+
+
+
{t('ghostery_dawn_onboarding_youve_successfully_set_up_your_browser')}
+ +
{`${t('ghostery_dawn_onboarding_surf_with_ease')}`}
+ + +
+
+ ); +}; + +export default SuccessView; diff --git a/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.scss b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.scss new file mode 100644 index 000000000..053f1c33d --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.scss @@ -0,0 +1,101 @@ +/** + * SuccessView Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +.SuccessView__container { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.SuccessView__relativeContainer { + position: relative; +} + +.SuccessView__caret.left { + margin: 7px auto 0 auto; + height: 10px; + width: 10px; + border-left: 2px solid $tundora; + border-top: 2px solid $tundora; + cursor: pointer; + transform: rotate(-45deg); +} + +.SuccessView__backContainer { + position: absolute; + display: flex; + width: 68px; + margin-top: 142px; + @include breakpoint(medium down) { + margin-top: 100px; + } + .SuccessView__back { + margin-top: 8px; + font-size: 16px; + color: $tundora; + text-decoration: underline; + } +} + +.SuccessView__title { + margin-top: 125px; + font-size: 24px; + font-weight: 500; + line-height: 2.33; + text-align: center; +} + +.SuccessView__subtitle { + margin-bottom: 47px; + width: 392px; + font-size: 18px; + line-height: 2.33; + text-align: center; +} + +.SuccessView__ghosterySuite { + margin-bottom: 77px; + height: 298px; + width: 545px; +} + +.SuccessView__ctaButton { + display: flex; + justify-content: center; + margin: auto; + min-height: 44px; + width: 162px; + padding: 7.7px 14px; + line-height: 22px; + background: linear-gradient( + 45deg, + #ff7e74 50%, + #00aef0 + ); + background-size: 200% 100%; + background-position: 100% 50%; + transition: 0.25s all; + border: none; + &:hover { + background-position: 0% 50%; + transition: 0.25s all; + } + color: #FFF; + font-size: 14.1px; + font-weight: 700; + border-radius: 3.5px; + text-align: center; + line-height: 2.05; + cursor: pointer; +} diff --git a/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/SuccessView.test.jsx b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/SuccessView.test.jsx new file mode 100644 index 000000000..761ef937d --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/SuccessView.test.jsx @@ -0,0 +1,37 @@ +/** + * SuccessView View Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { MemoryRouter } from 'react-router'; +import SuccessView from '../SuccessView'; + +const noop = () => {}; + +describe('app/dawn-hub/Views/OnboardingViews/Step0_SuccessView/SuccessView.test.jsx', () => { + describe('Snapshot tests with react-test-renderer', () => { + test('Success View is rendered correctly', () => { + const initialState = { + actions: { + sendPing: noop + } + }; + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/__snapshots__/SuccessView.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/__snapshots__/SuccessView.test.jsx.snap new file mode 100644 index 000000000..c6b8ecbbe --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/__tests__/__snapshots__/SuccessView.test.jsx.snap @@ -0,0 +1,53 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/dawn-hub/Views/OnboardingViews/Step0_SuccessView/SuccessView.test.jsx Snapshot tests with react-test-renderer Success View is rendered correctly 1`] = ` +Array [ + , +
+
+ ghostery_dawn_onboarding_youve_successfully_set_up_your_browser +
+
+ ghostery_dawn_onboarding_surf_with_ease +
+ + +
, +] +`; diff --git a/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/index.js b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/index.js new file mode 100644 index 000000000..9a25d9238 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/Step5_SuccessView/index.js @@ -0,0 +1,22 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Success View + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 SuccessView from './SuccessView'; +import { buildReduxHOC } from '../../../../shared-hub/utils'; +import sendPing from '../../../../shared-hub/actions/MetricsActions'; + +const actionCreators = { + sendPing +}; + +export default buildReduxHOC(null, actionCreators, SuccessView); diff --git a/app/dawn-hub/Views/OnboardingViews/StepNavigator/StepNavigator.jsx b/app/dawn-hub/Views/OnboardingViews/StepNavigator/StepNavigator.jsx new file mode 100644 index 000000000..71c1b76e3 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepNavigator/StepNavigator.jsx @@ -0,0 +1,58 @@ +/** + * Step Navigator Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** + * A React class component for rendering the StepNavigator, a container for all the views in a setup step + * @return {JSX} JSX for rendering the Step Progress bar of the Dawn Hub app + * @memberof DawnHubContainers + */ +class StepNavigator extends Component { + constructor(props) { + super(props); + this.state = { + screen: 0, + }; + } + + next = () => { + const { components } = this.props; + this.setState(state => ({ screen: (state.screen + 1) % components.length })); + } + + prev = () => { + const { components } = this.props; + this.setState(state => ({ screen: ((state.screen - 1) + components.length) % components.length })); + } + + render() { + const { components, step } = this.props; + const { screen } = this.state; + + const Screen = components[screen]; + + return ( + + ); + } +} + +// PropTypes ensure we pass required props of the correct type +StepNavigator.propTypes = { + step: PropTypes.string.isRequired, + components: PropTypes.arrayOf(PropTypes.elementType.isRequired).isRequired, +}; + +export default StepNavigator; diff --git a/app/dawn-hub/Views/OnboardingViews/StepNavigator/index.js b/app/dawn-hub/Views/OnboardingViews/StepNavigator/index.js new file mode 100644 index 000000000..008714688 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepNavigator/index.js @@ -0,0 +1,16 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Step Navigator + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 StepNavigator from './StepNavigator'; + +export default StepNavigator; diff --git a/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.jsx b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.jsx new file mode 100644 index 000000000..2fe8b1177 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.jsx @@ -0,0 +1,138 @@ +/** + * Step Progress Bar Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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, { Fragment } from 'react'; +import ClassNames from 'classnames'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; +import { + WELCOME, + ONBOARDING, + LOGIN, + BLOCK_SETTINGS, + CHOOSE_DEFAULT_SEARCH, + CHOOSE_PLAN +} from '../../OnboardingView/OnboardingConstants'; + +const steps = [ + { + label: t('sign_in'), + route: `/${ONBOARDING}/${LOGIN}`, + id: LOGIN + }, + { + label: t('ghostery_dawn_onboarding_privacy'), + route: `/${ONBOARDING}/${BLOCK_SETTINGS}`, + id: BLOCK_SETTINGS + }, + { + label: t('ghostery_dawn_onboarding_search'), + route: `/${ONBOARDING}/${CHOOSE_DEFAULT_SEARCH}`, + id: CHOOSE_DEFAULT_SEARCH + }, + { + label: t('ghostery_dawn_onboarding_plan'), + route: `/${ONBOARDING}/${CHOOSE_PLAN}`, + id: CHOOSE_PLAN + } +]; + +/** + * A React functional component for rendering the Step Progress bar + * @return {JSX} JSX for rendering the Step Progress bar of the Dawn Hub app + * @memberof DawnHubViews + */ +const StepProgressBar = (props) => { + const { currentStep } = props; + const currentStepNumber = parseInt(currentStep, 10); + const totalSteps = steps.length; + + const progressBarContainerClasses = ClassNames('StepProgressBarContainer', { + step4: currentStepNumber === 4 + }); + + const logoutIfStepOne = (stepId) => { + const { actions } = props; + const { logout } = actions; + if (stepId === LOGIN) logout(); + }; + + const renderStep = (step, isCurrent, stepClass) => { + const labelClasses = ClassNames('StepProgressBar__label', { + current: isCurrent, + }); + const stepClasses = ClassNames('StepProgressBar__Step', stepClass, { + [`step-${step.id}`]: stepClass !== 'step-completed', + }); + + if (stepClass === 'incomplete') { + return ( +
+
{step.label}
+
+
+ ); + } + + return ( +
+ logoutIfStepOne(step.id)}> +
{step.label}
+
+ +
+ ); + }; + + const renderCompletedStep = step => renderStep(step, false, 'step-completed'); + + const renderCurrentStep = step => renderStep(step, true, 'current'); + + const renderIncompleteStep = step => renderStep(step, false, 'incomplete'); + + const renderProgressBar = () => ( + steps.map((value, index) => { + const step = index + 1; + + return ( + + {(step < currentStepNumber) && renderCompletedStep(steps[index])} + {(step === currentStepNumber) && renderCurrentStep(steps[index])} + {(step > currentStepNumber) && renderIncompleteStep(steps[index])} + {(step !== totalSteps) && ( + + {(step < currentStepNumber) && ( +
+ )} + {(step >= currentStepNumber) && ( +
+ )} + + )} + + ); + }) + ); + + return ( +
+ {(currentStep !== WELCOME) && renderProgressBar()} +
+ ); +}; +// PropTypes ensure we pass required props of the correct type +StepProgressBar.propTypes = { + currentStep: PropTypes.string.isRequired, +}; + +export default StepProgressBar; diff --git a/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.scss b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.scss new file mode 100644 index 000000000..5eeee3d12 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.scss @@ -0,0 +1,116 @@ +/** + * Step Progress Bar Sass + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + */ + +.StepProgressBarContainer { + margin-top: 40px; + display: flex; + justify-content: space-around; + align-items: center; + max-width: 800px; + margin: auto; + padding-top: 10px; + + &.step4 { + max-width: 724px; + padding: 10px 20px 0 20px; + } +} + +.StepProgressBar__Step { + height: 34px; + width: 32px; + margin: auto; + + &.step-1 { + &.current { + background: url('/app/images/hub/step-progress-bar/step-1-current.svg'); + background-repeat: no-repeat; + } + } + &.step-2 { + &.current { + background: url('/app/images/hub/step-progress-bar/step-2-current.svg'); + background-repeat: no-repeat; + } + &.incomplete { + background: url('/app/images/hub/step-progress-bar/step-2-incomplete.svg'); + background-repeat: no-repeat; + margin: 0 2px 3px 0; + height: 32px; + } + } + &.step-3 { + &.current { + background: url('/app/images/hub/step-progress-bar/step-3-current.svg'); + background-repeat: no-repeat; + } + &.incomplete { + background: url('/app/images/hub/step-progress-bar/step-3-incomplete.svg'); + background-repeat: no-repeat; + margin: 0 2px 3px 0; + height: 32px; + } + } + &.step-4 { + &.current { + background: url('/app/images/hub/step-progress-bar/step-4-current.svg'); + background-repeat: no-repeat; + } + &.incomplete { + background: url('/app/images/hub/step-progress-bar/step-4-incomplete.svg'); + background-repeat: no-repeat; + margin: 0 2px 3px 0; + height: 32px; + } + } + &.step-completed { + background: url('/app/images/hub/step-progress-bar/step-completed.svg'); + background-repeat: no-repeat; + } +} +.StepProgressBar__column { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 45px; + text-align: center; +} +.StepProgressBar__label { + margin-bottom: 14px; + font-size: 12px; + color: $tundora; + white-space: nowrap; + &.current { + font-weight: 700; + } +} +.StepProgressBar__line { + margin-top: 27px; + width: 100%; + &.completed { + border: solid 2px $ghosty-blue; + width: 110%; + } + &.incompleted { + padding: 13px; + background-image: + radial-gradient(circle at 2.5px, $ghosty-blue 1.25px, rgba(255,255,255,0) 2.5px), + radial-gradient(circle, $ghosty-blue 1.25px, rgba(255,255,255,0) 2.5px), + radial-gradient(circle at 2.5px, $ghosty-blue 1.25px, rgba(255,255,255,0) 2.5px), + radial-gradient(circle, $ghosty-blue 1.25px, rgba(255,255,255,0) 2.5px); + background-position: top, right, bottom, left; + background-size: 12px 25px, 0px 0px; + background-repeat: repeat-x, repeat-y; + } +} diff --git a/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/StepProgressBar.test.jsx b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/StepProgressBar.test.jsx new file mode 100644 index 000000000..c0f438584 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/StepProgressBar.test.jsx @@ -0,0 +1,93 @@ +/** + * StepProgressBar Test Component + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 renderer from 'react-test-renderer'; +import { shallow } from 'enzyme'; +import { MemoryRouter } from 'react-router'; +import StepProgressBar from '../StepProgressBar'; +import { WELCOME, LOGIN, BLOCK_SETTINGS, CHOOSE_DEFAULT_SEARCH, CHOOSE_PLAN, SUCCESS } from '../../../OnboardingView/OnboardingConstants'; + +const noop = () => {}; + +describe('app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx', () => { + const initialState = { + currentStep: LOGIN, + actions: { + logout: noop + } + }; + describe('Snapshot tests with react-test-renderer', () => { + test('StepProgressBar is rendered correctly', () => { + const component = renderer.create( + + + + ).toJSON(); + expect(component).toMatchSnapshot(); + }); + }); + + describe('Shallow snapshot tests rendered with Enzyme', () => { + test('StepProgressBar View step 0', () => { + const step_0_initialState = { + ...initialState, + currentStep: WELCOME, + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + test('StepProgressBar View step 1', () => { + const step_1_initialState = { + ...initialState, + currentStep: LOGIN + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + test('StepProgressBar View step 2', () => { + const step_2_initialState = { + ...initialState, + currentStep: BLOCK_SETTINGS + }; + + const component = shallow(); + }); + + test('StepProgressBar View step 4', () => { + const step_4_initialState = { + currentStep: CHOOSE_PLAN + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + test('StepProgressBar View step 5', () => { + const step_5_initialState = { + ...initialState, + currentStep: SUCCESS + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + }); +}); diff --git a/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/__snapshots__/StepProgressBar.test.jsx.snap b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/__snapshots__/StepProgressBar.test.jsx.snap new file mode 100644 index 000000000..340afb482 --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/__tests__/__snapshots__/StepProgressBar.test.jsx.snap @@ -0,0 +1,79 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx Shallow snapshot tests rendered with Enzyme StepProgressBar View step 0 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx Shallow snapshot tests rendered with Enzyme StepProgressBar View step 1 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx Shallow snapshot tests rendered with Enzyme StepProgressBar View step 4 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx Shallow snapshot tests rendered with Enzyme StepProgressBar View step 5 1`] = `ShallowWrapper {}`; + +exports[`app/dawn-hub/Views/OnboardingViews/StepProgressBar/StepProgressBar.test.jsx Snapshot tests with react-test-renderer StepProgressBar is rendered correctly 1`] = ` +
+
+ +
+ sign_in +
+
+ +
+
+
+
+ ghostery_dawn_onboarding_privacy +
+
+
+
+
+
+ ghostery_dawn_onboarding_search +
+
+
+
+
+
+ ghostery_dawn_onboarding_plan +
+
+
+
+`; diff --git a/app/dawn-hub/Views/OnboardingViews/StepProgressBar/index.js b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/index.js new file mode 100644 index 000000000..e3394b2ef --- /dev/null +++ b/app/dawn-hub/Views/OnboardingViews/StepProgressBar/index.js @@ -0,0 +1,22 @@ +/** + * Point of entry index.js file for Dawn Hub onboarding flow Step Progress Bar + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 StepProgressBar from './StepProgressBar'; +import { buildReduxHOC } from '../../../../shared-hub/utils'; +import { logout } from '../../../../Account/AccountActions'; + +const actionCreators = { + logout +}; + +export default buildReduxHOC(null, actionCreators, StepProgressBar); diff --git a/app/dawn-hub/createStore.js b/app/dawn-hub/createStore.js new file mode 100644 index 000000000..e9b289bd4 --- /dev/null +++ b/app/dawn-hub/createStore.js @@ -0,0 +1,38 @@ +/** + * Dawn Hub onboarding flow Redux Store Init + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + * + * @namespace GhosteryBrowserHubReactStore + */ + +import thunk from 'redux-thunk'; + +import { makeStoreCreator } from '../shared-hub/utils/index'; + +import toast from '../shared-hub/reducers/ToastReducer'; +import antiSuite from '../shared-hub/reducers/AntiSuiteReducer'; +import blockingPolicy from '../shared-hub/reducers/BlockingPolicyReducer'; +import defaultSearch from './Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchReducer'; +import setupLifecycle from '../shared-hub/reducers/SetupLifecycleReducer'; +import account from '../Account/AccountReducer'; +import settings from '../panel/reducers/settings'; + +const reducers = { + toast, + antiSuite, + blockingPolicy, + defaultSearch, + setupLifecycle, + account, + settings +}; + +export default makeStoreCreator(reducers, [thunk]); diff --git a/app/dawn-hub/index.jsx b/app/dawn-hub/index.jsx new file mode 100644 index 000000000..8c256f91a --- /dev/null +++ b/app/dawn-hub/index.jsx @@ -0,0 +1,46 @@ +/** + * Dawn Hub React App Init + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 + * + * @namespace DawnHubViews + */ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { HashRouter as Router, Route } from 'react-router-dom'; + +import createStore from './createStore'; + +// Views +import AppView from './Views/AppView'; +import OnboardingView from './Views/OnboardingView'; + +const store = createStore(); + +/** + * Top-Level view for the Dawn Hub + * @memberof DawnHubViews + */ +const Hub = () => ( + + + +); + +ReactDOM.render( + ( + + + + + + ), document.getElementById('root'), +); diff --git a/app/hub/Views/AppView/__tests__/__snapshots__/AppView.test.jsx.snap b/app/hub/Views/AppView/__tests__/__snapshots__/AppView.test.jsx.snap index f6369e1a9..ee1da5b3b 100644 --- a/app/hub/Views/AppView/__tests__/__snapshots__/AppView.test.jsx.snap +++ b/app/hub/Views/AppView/__tests__/__snapshots__/AppView.test.jsx.snap @@ -11,7 +11,7 @@ exports[`app/hub/Views/AppView component Snapshot tests with react-test-renderer className="App__mainContent full-height full-width" >
- Example toast message +
+ Example toast message +
new Promise((resolve, re } })); -describe('app/hub/Views/SetupView/ actions', () => { +describe('app/hub/Views/OnboardingView/ actions', () => { test('getHomeProps action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupView/__tests__/SetupView.test.jsx b/app/hub/Views/SetupView/__tests__/SetupView.test.jsx index 8321e856e..2a1521201 100644 --- a/app/hub/Views/SetupView/__tests__/SetupView.test.jsx +++ b/app/hub/Views/SetupView/__tests__/SetupView.test.jsx @@ -33,7 +33,7 @@ const ExampleComponent = props => (
example component
); -describe('app/hub/Views/SetupView component', () => { +describe('app/hub/Views/OnboardingView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup view is rendered correctly on first route', () => { const initialState = { diff --git a/app/hub/Views/SetupView/__tests__/SetupViewActions.test.js b/app/hub/Views/SetupView/__tests__/SetupViewActions.test.js index 12f374c77..f3921cc2e 100644 --- a/app/hub/Views/SetupView/__tests__/SetupViewActions.test.js +++ b/app/hub/Views/SetupView/__tests__/SetupViewActions.test.js @@ -45,7 +45,7 @@ utils.sendMessageInPromise = jest.fn((name, message) => new Promise((resolve, re } })); -describe('app/hub/Views/SetupView/ actions', () => { +describe('app/hub/Views/OnboardingView/ actions', () => { test('getSetupShowWarningOverride action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupView/__tests__/SetupViewContainer.test.jsx b/app/hub/Views/SetupView/__tests__/SetupViewContainer.test.jsx index 14939e74a..785256a80 100644 --- a/app/hub/Views/SetupView/__tests__/SetupViewContainer.test.jsx +++ b/app/hub/Views/SetupView/__tests__/SetupViewContainer.test.jsx @@ -49,7 +49,7 @@ const actions = { setSetupComplete: () => {}, }; -describe('app/hub/Views/SetupView container', () => { +describe('app/hub/Views/OnboardingView container', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup view container is rendered correctly on the Blocking step', () => { const paths = ['/setup/1', '/setup/1/custom', '/setup/2', '/setup/3', '/setup/4']; diff --git a/app/hub/Views/SetupView/__tests__/SetupViewReducer.test.js b/app/hub/Views/SetupView/__tests__/SetupViewReducer.test.js index 820922cb3..a1938abe9 100644 --- a/app/hub/Views/SetupView/__tests__/SetupViewReducer.test.js +++ b/app/hub/Views/SetupView/__tests__/SetupViewReducer.test.js @@ -48,7 +48,7 @@ const initialState = Immutable({ }, }); -describe('app/hub/Views/SetupView reducer', () => { +describe('app/hub/Views/OnboardingView reducer', () => { test('initial state is correct', () => { expect(SetupViewReducer(undefined, {})).toEqual({}); }); diff --git a/app/hub/Views/SetupView/__tests__/__snapshots__/SetupView.test.jsx.snap b/app/hub/Views/SetupView/__tests__/__snapshots__/SetupView.test.jsx.snap index dd6b69d36..ba5dd346f 100644 --- a/app/hub/Views/SetupView/__tests__/__snapshots__/SetupView.test.jsx.snap +++ b/app/hub/Views/SetupView/__tests__/__snapshots__/SetupView.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupView component More Snapshot tests with react-test-renderer, but for edge cases edge case where activeIndex not in steps index 1`] = ` +exports[`app/hub/Views/OnboardingView component More Snapshot tests with react-test-renderer, but for edge cases edge case where activeIndex not in steps index 1`] = `
@@ -13,7 +13,7 @@ exports[`app/hub/Views/SetupView component More Snapshot tests with react-test-r
`; -exports[`app/hub/Views/SetupView component More Snapshot tests with react-test-renderer, but for edge cases edge case where steps is empty array 1`] = ` +exports[`app/hub/Views/OnboardingView component More Snapshot tests with react-test-renderer, but for edge cases edge case where steps is empty array 1`] = `
@@ -26,7 +26,7 @@ exports[`app/hub/Views/SetupView component More Snapshot tests with react-test-r
`; -exports[`app/hub/Views/SetupView component Snapshot tests with react-test-renderer setup view is rendered correctly on first and extra route 1`] = ` +exports[`app/hub/Views/OnboardingView component Snapshot tests with react-test-renderer setup view is rendered correctly on first and extra route 1`] = `
@@ -51,7 +51,7 @@ exports[`app/hub/Views/SetupView component Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView component Snapshot tests with react-test-renderer setup view is rendered correctly on first route 1`] = ` +exports[`app/hub/Views/OnboardingView component Snapshot tests with react-test-renderer setup view is rendered correctly on first route 1`] = `
@@ -73,7 +73,7 @@ exports[`app/hub/Views/SetupView component Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView component Snapshot tests with react-test-renderer setup view is rendered correctly on last route 1`] = ` +exports[`app/hub/Views/OnboardingView component Snapshot tests with react-test-renderer setup view is rendered correctly on last route 1`] = `
diff --git a/app/hub/Views/SetupView/__tests__/__snapshots__/SetupViewContainer.test.jsx.snap b/app/hub/Views/SetupView/__tests__/__snapshots__/SetupViewContainer.test.jsx.snap index c7a2ca3ee..6b5dac85b 100644 --- a/app/hub/Views/SetupView/__tests__/__snapshots__/SetupViewContainer.test.jsx.snap +++ b/app/hub/Views/SetupView/__tests__/__snapshots__/SetupViewContainer.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Anti-Suite step 1`] = ` +exports[`app/hub/Views/OnboardingView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Anti-Suite step 1`] = `
@@ -52,7 +52,7 @@ exports[`app/hub/Views/SetupView container Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Blocking step 1`] = ` +exports[`app/hub/Views/OnboardingView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Blocking step 1`] = `
@@ -104,7 +104,7 @@ exports[`app/hub/Views/SetupView container Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Custom Blocking step 1`] = ` +exports[`app/hub/Views/OnboardingView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Custom Blocking step 1`] = `
@@ -159,7 +159,7 @@ exports[`app/hub/Views/SetupView container Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Done step 1`] = ` +exports[`app/hub/Views/OnboardingView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Done step 1`] = `
@@ -211,7 +211,7 @@ exports[`app/hub/Views/SetupView container Snapshot tests with react-test-render
`; -exports[`app/hub/Views/SetupView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Human Web step 1`] = ` +exports[`app/hub/Views/OnboardingView container Snapshot tests with react-test-renderer setup view container is rendered correctly on the Human Web step 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteView.test.jsx b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteView.test.jsx index 0099d4fd5..b3bf3ae9f 100644 --- a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteView.test.jsx +++ b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteView.test.jsx @@ -16,7 +16,7 @@ import renderer from 'react-test-renderer'; import { shallow } from 'enzyme'; import SetupAntiSuiteView from '../SetupAntiSuiteView'; -describe('app/hub/Views/SetupViews/SetupAntiSuiteView component', () => { +describe('app/hub/Views/OnboardingViews/SetupAntiSuiteView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup anti-suite view is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteViewActions.test.js b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteViewActions.test.js index 7f545857e..80403a1fb 100644 --- a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteViewActions.test.js +++ b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/SetupAntiSuiteViewActions.test.js @@ -43,7 +43,7 @@ utils.sendMessageInPromise = jest.fn((name, message) => new Promise((resolve, re } })); -describe('app/hub/Views/SetupViews/SetupAntiSuiteView actions', () => { +describe('app/hub/Views/OnboardingViews/SetupAntiSuiteView actions', () => { test('setAntiTracking action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/__snapshots__/SetupAntiSuiteView.test.jsx.snap b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/__snapshots__/SetupAntiSuiteView.test.jsx.snap index ae5d25fe6..38e132141 100644 --- a/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/__snapshots__/SetupAntiSuiteView.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupAntiSuiteView/__tests__/__snapshots__/SetupAntiSuiteView.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupAntiSuiteView component More Snapshot tests with react-test-renderer, but for edge cases edge case where features is an empty array 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupAntiSuiteView component More Snapshot tests with react-test-renderer, but for edge cases edge case where features is an empty array 1`] = `
@@ -10,7 +10,7 @@ exports[`app/hub/Views/SetupViews/SetupAntiSuiteView component More Snapshot tes
`; -exports[`app/hub/Views/SetupViews/SetupAntiSuiteView component Snapshot tests with react-test-renderer setup anti-suite view is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupAntiSuiteView component Snapshot tests with react-test-renderer setup anti-suite view is rendered correctly 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/SetupBlockingDropdown.test.jsx b/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/SetupBlockingDropdown.test.jsx index ecd35cde4..1214f2e17 100644 --- a/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/SetupBlockingDropdown.test.jsx +++ b/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/SetupBlockingDropdown.test.jsx @@ -20,7 +20,7 @@ import { ToastMessage } from '../../../../../shared-components'; // Mock Necessary Imports jest.mock('../../../../../panel/components/Settings/GlobalBlocking', () => props =>
Mock Global Blocking
); -describe('app/hub/Views/SetupViews/SetupBlockingDropdown component', () => { +describe('app/hub/Views/OnboardingViews/SetupBlockingDropdown component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup blocking dropdown component is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/__snapshots__/SetupBlockingDropdown.test.jsx.snap b/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/__snapshots__/SetupBlockingDropdown.test.jsx.snap index 1bbb9a35c..48144a180 100644 --- a/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/__snapshots__/SetupBlockingDropdown.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupBlockingDropdown/__tests__/__snapshots__/SetupBlockingDropdown.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupBlockingDropdown component Snapshot tests with react-test-renderer setup blocking dropdown component is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupBlockingDropdown component Snapshot tests with react-test-renderer setup blocking dropdown component is rendered correctly 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingView.test.jsx b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingView.test.jsx index fa76c3e6f..5daa97fd4 100644 --- a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingView.test.jsx +++ b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingView.test.jsx @@ -16,7 +16,7 @@ import renderer from 'react-test-renderer'; import { shallow } from 'enzyme'; import SetupBlockingView from '../SetupBlockingView'; -describe('app/hub/Views/SetupViews/SetupBlockingView component', () => { +describe('app/hub/Views/OnboardingViews/SetupBlockingView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup blocking view is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingViewActions.test.js b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingViewActions.test.js index cfa80c6f0..238b35d84 100644 --- a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingViewActions.test.js +++ b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/SetupBlockingViewActions.test.js @@ -31,7 +31,7 @@ utils.sendMessageInPromise = jest.fn((name, message) => new Promise((resolve, re } })); -describe('app/hub/Views/SetupViews/SetupBlockingView actions', () => { +describe('app/hub/Views/OnboardingViews/SetupBlockingView actions', () => { test('setBlockingPolicy action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/__snapshots__/SetupBlockingView.test.jsx.snap b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/__snapshots__/SetupBlockingView.test.jsx.snap index 10bd7022a..c6059f7a7 100644 --- a/app/hub/Views/SetupViews/SetupBlockingView/__tests__/__snapshots__/SetupBlockingView.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupBlockingView/__tests__/__snapshots__/SetupBlockingView.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupBlockingView component More Snapshot tests with react-test-renderer, but for edge cases edge case where choices is an empty array 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupBlockingView component More Snapshot tests with react-test-renderer, but for edge cases edge case where choices is an empty array 1`] = `
@@ -18,7 +18,7 @@ exports[`app/hub/Views/SetupViews/SetupBlockingView component More Snapshot test
`; -exports[`app/hub/Views/SetupViews/SetupBlockingView component Snapshot tests with react-test-renderer setup blocking view is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupBlockingView component Snapshot tests with react-test-renderer setup blocking view is rendered correctly 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneView.test.jsx b/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneView.test.jsx index b23bc9cc9..826d47ea4 100644 --- a/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneView.test.jsx +++ b/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneView.test.jsx @@ -17,7 +17,7 @@ import { shallow } from 'enzyme'; import { MemoryRouter } from 'react-router'; import SetupDoneView from '../SetupDoneView'; -describe('app/hub/Views/SetupViews/SetupDoneView component', () => { +describe('app/hub/Views/OnboardingViews/SetupDoneView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup human web view is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneViewActions.test.js b/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneViewActions.test.js index 1e8a99a74..e51a5ebe6 100644 --- a/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneViewActions.test.js +++ b/app/hub/Views/SetupViews/SetupDoneView/__tests__/SetupDoneViewActions.test.js @@ -31,7 +31,7 @@ utils.sendMessageInPromise = jest.fn((name, message) => new Promise((resolve, re } })); -describe('app/hub/Views/SetupViews/SetupDoneView actions', () => { +describe('app/hub/Views/OnboardingViews/SetupDoneView actions', () => { test('setSetupComplete action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupViews/SetupDoneView/__tests__/__snapshots__/SetupDoneView.test.jsx.snap b/app/hub/Views/SetupViews/SetupDoneView/__tests__/__snapshots__/SetupDoneView.test.jsx.snap index 6509da93e..30f6e540b 100644 --- a/app/hub/Views/SetupViews/SetupDoneView/__tests__/__snapshots__/SetupDoneView.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupDoneView/__tests__/__snapshots__/SetupDoneView.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupDoneView component Snapshot tests with react-test-renderer setup human web view is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupDoneView component Snapshot tests with react-test-renderer setup human web view is rendered correctly 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupHeader/__tests__/SetupHeader.test.jsx b/app/hub/Views/SetupViews/SetupHeader/__tests__/SetupHeader.test.jsx index e1fe0bbcb..a973feec7 100644 --- a/app/hub/Views/SetupViews/SetupHeader/__tests__/SetupHeader.test.jsx +++ b/app/hub/Views/SetupViews/SetupHeader/__tests__/SetupHeader.test.jsx @@ -16,7 +16,7 @@ import renderer from 'react-test-renderer'; import { shallow } from 'enzyme'; import SetupHeader from '../SetupHeader'; -describe('app/hub/Views/SetupViews/SetupHeader component', () => { +describe('app/hub/Views/OnboardingViews/SetupHeader component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup header is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupHeader/__tests__/__snapshots__/SetupHeader.test.jsx.snap b/app/hub/Views/SetupViews/SetupHeader/__tests__/__snapshots__/SetupHeader.test.jsx.snap index 8b72c56c1..86061f3b4 100644 --- a/app/hub/Views/SetupViews/SetupHeader/__tests__/__snapshots__/SetupHeader.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupHeader/__tests__/__snapshots__/SetupHeader.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupHeader component Snapshot tests with react-test-renderer setup header is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupHeader component Snapshot tests with react-test-renderer setup header is rendered correctly 1`] = `
diff --git a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebView.test.jsx b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebView.test.jsx index 2a9b48bc3..804477b95 100644 --- a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebView.test.jsx +++ b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebView.test.jsx @@ -16,7 +16,7 @@ import renderer from 'react-test-renderer'; import { shallow } from 'enzyme'; import SetupHumanWebView from '../SetupHumanWebView'; -describe('app/hub/Views/SetupViews/SetupHumanWebView component', () => { +describe('app/hub/Views/OnboardingViews/SetupHumanWebView component', () => { describe('Snapshot tests with react-test-renderer', () => { test('setup human web view is rendered correctly', () => { const initialState = { diff --git a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebViewActions.test.js b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebViewActions.test.js index 3b21c84be..acb223581 100644 --- a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebViewActions.test.js +++ b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/SetupHumanWebViewActions.test.js @@ -31,7 +31,7 @@ utils.sendMessageInPromise = jest.fn((name, message) => new Promise((resolve, re } })); -describe('app/hub/Views/SetupViews/SetupHumanWebView actions', () => { +describe('app/hub/Views/OnboardingViews/SetupHumanWebView actions', () => { test('setHumanWeb action should return correctly', () => { const initialState = {}; const store = mockStore(initialState); diff --git a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/__snapshots__/SetupHumanWebView.test.jsx.snap b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/__snapshots__/SetupHumanWebView.test.jsx.snap index a6ad2f881..6b94ff87b 100644 --- a/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/__snapshots__/SetupHumanWebView.test.jsx.snap +++ b/app/hub/Views/SetupViews/SetupHumanWebView/__tests__/__snapshots__/SetupHumanWebView.test.jsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`app/hub/Views/SetupViews/SetupHumanWebView component Snapshot tests with react-test-renderer setup human web view is rendered correctly 1`] = ` +exports[`app/hub/Views/OnboardingViews/SetupHumanWebView component Snapshot tests with react-test-renderer setup human web view is rendered correctly 1`] = `
diff --git a/app/hub/Views/UpgradePlanView/UpgradePlanView.scss b/app/hub/Views/UpgradePlanView/UpgradePlanView.scss index 0d75ecd9e..ec4390f48 100644 --- a/app/hub/Views/UpgradePlanView/UpgradePlanView.scss +++ b/app/hub/Views/UpgradePlanView/UpgradePlanView.scss @@ -1,11 +1,3 @@ -$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 { diff --git a/app/images/hub/ChooseDefaultSearchView/dropdown-caret.svg b/app/images/hub/ChooseDefaultSearchView/dropdown-caret.svg new file mode 100644 index 000000000..d0ce33f4d --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/dropdown-caret.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/images/hub/ChooseDefaultSearchView/ghostery-browser-logo.svg b/app/images/hub/ChooseDefaultSearchView/ghostery-browser-logo.svg new file mode 100644 index 000000000..40de7e6ea --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/ghostery-browser-logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/ChooseDefaultSearchView/search-engine-logo-bing.svg b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-bing.svg new file mode 100644 index 000000000..9e3a092b1 --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-bing.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/ChooseDefaultSearchView/search-engine-logo-ghostery.svg b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-ghostery.svg new file mode 100644 index 000000000..7d682ffa3 --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-ghostery.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/ChooseDefaultSearchView/search-engine-logo-startpage.svg b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-startpage.svg new file mode 100644 index 000000000..96ba1cf85 --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-startpage.svg @@ -0,0 +1,9 @@ + + + Startpage-Logo + + + + + + \ No newline at end of file diff --git a/app/images/hub/ChooseDefaultSearchView/search-engine-logo-yahoo.svg b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-yahoo.svg new file mode 100644 index 000000000..9bc76d1c9 --- /dev/null +++ b/app/images/hub/ChooseDefaultSearchView/search-engine-logo-yahoo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/app/images/hub/browser-create-account-view/ghosty-box.svg b/app/images/hub/browser-create-account-view/ghosty-box.svg new file mode 100644 index 000000000..c2fe79ee6 --- /dev/null +++ b/app/images/hub/browser-create-account-view/ghosty-box.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/hub/browser-create-account-view/ghosty-shield.svg b/app/images/hub/browser-create-account-view/ghosty-shield.svg new file mode 100644 index 000000000..6c7d7cc2d --- /dev/null +++ b/app/images/hub/browser-create-account-view/ghosty-shield.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/images/hub/setup/info.svg b/app/images/hub/setup/info.svg new file mode 100644 index 000000000..7bcbcb9ef --- /dev/null +++ b/app/images/hub/setup/info.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/step-progress-bar/step-1-current.svg b/app/images/hub/step-progress-bar/step-1-current.svg new file mode 100644 index 000000000..ea7621dde --- /dev/null +++ b/app/images/hub/step-progress-bar/step-1-current.svg @@ -0,0 +1,8 @@ + + + + + 1 + + + diff --git a/app/images/hub/step-progress-bar/step-2-current.svg b/app/images/hub/step-progress-bar/step-2-current.svg new file mode 100644 index 000000000..7b2b0d76f --- /dev/null +++ b/app/images/hub/step-progress-bar/step-2-current.svg @@ -0,0 +1,8 @@ + + + + + 2 + + + diff --git a/app/images/hub/step-progress-bar/step-2-incomplete.svg b/app/images/hub/step-progress-bar/step-2-incomplete.svg new file mode 100644 index 000000000..f53cb0b71 --- /dev/null +++ b/app/images/hub/step-progress-bar/step-2-incomplete.svg @@ -0,0 +1,8 @@ + + + + + 2 + + + diff --git a/app/images/hub/step-progress-bar/step-3-current.svg b/app/images/hub/step-progress-bar/step-3-current.svg new file mode 100644 index 000000000..a1e47f728 --- /dev/null +++ b/app/images/hub/step-progress-bar/step-3-current.svg @@ -0,0 +1,8 @@ + + + + + 3 + + + diff --git a/app/images/hub/step-progress-bar/step-3-incomplete.svg b/app/images/hub/step-progress-bar/step-3-incomplete.svg new file mode 100644 index 000000000..3b0ec21f0 --- /dev/null +++ b/app/images/hub/step-progress-bar/step-3-incomplete.svg @@ -0,0 +1,12 @@ + + + + + + + 3 + + + + + diff --git a/app/images/hub/step-progress-bar/step-4-current.svg b/app/images/hub/step-progress-bar/step-4-current.svg new file mode 100644 index 000000000..eeed221c5 --- /dev/null +++ b/app/images/hub/step-progress-bar/step-4-current.svg @@ -0,0 +1,8 @@ + + + + + 4 + + + diff --git a/app/images/hub/step-progress-bar/step-4-incomplete.svg b/app/images/hub/step-progress-bar/step-4-incomplete.svg new file mode 100644 index 000000000..26322cabe --- /dev/null +++ b/app/images/hub/step-progress-bar/step-4-incomplete.svg @@ -0,0 +1,8 @@ + + + + + 4 + + + diff --git a/app/images/hub/step-progress-bar/step-completed.svg b/app/images/hub/step-progress-bar/step-completed.svg new file mode 100644 index 000000000..531837424 --- /dev/null +++ b/app/images/hub/step-progress-bar/step-completed.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/success/ghostery-suite.png b/app/images/hub/success/ghostery-suite.png new file mode 100644 index 000000000..2fabf948e Binary files /dev/null and b/app/images/hub/success/ghostery-suite.png differ diff --git a/app/images/hub/toast/toast-alert.svg b/app/images/hub/toast/toast-alert.svg new file mode 100644 index 000000000..693f1643b --- /dev/null +++ b/app/images/hub/toast/toast-alert.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/toast/toast-success.svg b/app/images/hub/toast/toast-success.svg new file mode 100644 index 000000000..7885c19e8 --- /dev/null +++ b/app/images/hub/toast/toast-success.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/images/hub/welcome/rocketShip.png b/app/images/hub/welcome/rocketShip.png new file mode 100644 index 000000000..1982e4fb6 Binary files /dev/null and b/app/images/hub/welcome/rocketShip.png differ diff --git a/app/panel-android/components/content/__tests__/OverviewTab.jsx b/app/panel-android/components/content/__tests__/OverviewTab.jsx index 4fec3f938..411a9d52a 100644 --- a/app/panel-android/components/content/__tests__/OverviewTab.jsx +++ b/app/panel-android/components/content/__tests__/OverviewTab.jsx @@ -16,7 +16,7 @@ import renderer from 'react-test-renderer'; import { mount } from 'enzyme'; import OverviewTab from '../OverviewTab'; -jest.mock('../../../../panel/components/Tooltip'); +jest.mock('../../../../shared-components/Tooltip'); describe('app/panel-android/components/content/OverviewTab.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { diff --git a/app/panel/components/BuildingBlocks/CliqzFeature.jsx b/app/panel/components/BuildingBlocks/CliqzFeature.jsx index 3abc03dc9..ca3053c88 100644 --- a/app/panel/components/BuildingBlocks/CliqzFeature.jsx +++ b/app/panel/components/BuildingBlocks/CliqzFeature.jsx @@ -14,7 +14,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ClassNames from 'classnames'; -import Tooltip from '../Tooltip'; +import Tooltip from '../../../shared-components/Tooltip'; /** * @class Implements rendering and interaction for Cliqz feature icon toggles diff --git a/app/panel/components/BuildingBlocks/DonutGraph.jsx b/app/panel/components/BuildingBlocks/DonutGraph.jsx index defd5607f..0bc49cfb4 100644 --- a/app/panel/components/BuildingBlocks/DonutGraph.jsx +++ b/app/panel/components/BuildingBlocks/DonutGraph.jsx @@ -23,7 +23,7 @@ import { scaleLinear, select } from 'd3'; -import Tooltip from '../Tooltip'; +import Tooltip from '../../../shared-components/Tooltip'; /** * @class Generate donut graph. Used to display tracker data in the Summary View. diff --git a/app/panel/components/BuildingBlocks/GhosteryFeature.jsx b/app/panel/components/BuildingBlocks/GhosteryFeature.jsx index d66e943bd..679f5bae3 100644 --- a/app/panel/components/BuildingBlocks/GhosteryFeature.jsx +++ b/app/panel/components/BuildingBlocks/GhosteryFeature.jsx @@ -14,7 +14,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ClassNames from 'classnames'; -import Tooltip from '../Tooltip'; +import Tooltip from '../../../shared-components/Tooltip'; import globals from '../../../../src/classes/Globals'; const { BLACKLISTED, WHITELISTED } = globals; diff --git a/app/panel/components/BuildingBlocks/PauseButton.jsx b/app/panel/components/BuildingBlocks/PauseButton.jsx index d7ab0e287..f02f8bdf2 100644 --- a/app/panel/components/BuildingBlocks/PauseButton.jsx +++ b/app/panel/components/BuildingBlocks/PauseButton.jsx @@ -14,7 +14,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import ClassNames from 'classnames'; -import Tooltip from '../Tooltip'; +import Tooltip from '../../../shared-components/Tooltip'; /** * @class Implements the Pause button on the Summary view. diff --git a/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx b/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx index cba12eac1..e2503a56e 100644 --- a/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx +++ b/app/panel/components/BuildingBlocks/__tests__/CliqzFeature.jsx @@ -22,7 +22,7 @@ global.t = function(str) { }; // Fake the Tooltip implementation -jest.mock('../../Tooltip'); +jest.mock('../../../../shared-components/Tooltip'); describe('app/panel/components/CliqzFeature.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { diff --git a/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx b/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx index 0968d0058..80891ef8e 100644 --- a/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx +++ b/app/panel/components/BuildingBlocks/__tests__/DonutGraph.jsx @@ -22,7 +22,7 @@ global.t = function(str) { }; // Fake the Tooltip implementation -jest.mock('../../Tooltip'); +jest.mock('../../../../shared-components/Tooltip'); describe('app/panel/components/DonutGraph.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { diff --git a/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx b/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx index e8516f147..30b96e109 100644 --- a/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx +++ b/app/panel/components/BuildingBlocks/__tests__/GhosteryFeature.jsx @@ -22,7 +22,7 @@ global.t = function(str) { }; // Fake the Tooltip implementation -jest.mock('../../Tooltip'); +jest.mock('../../../../shared-components/Tooltip'); describe('app/panel/components/GhosteryFeature.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { diff --git a/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx b/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx index 37ba604ee..6855ad663 100644 --- a/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx +++ b/app/panel/components/BuildingBlocks/__tests__/PauseButton.jsx @@ -22,7 +22,7 @@ global.t = function(str) { }; // Fake the Tooltip implementation -jest.mock('../../Tooltip'); +jest.mock('../../../../shared-components/Tooltip'); describe('app/panel/components/BuildingBlocks/PauseButton.jsx', () => { describe('Snapshot tests with react-test-renderer', () => { diff --git a/app/panel/components/BuildingBlocks/index.js b/app/panel/components/BuildingBlocks/index.js index 8f73d6052..edc925cbc 100644 --- a/app/panel/components/BuildingBlocks/index.js +++ b/app/panel/components/BuildingBlocks/index.js @@ -21,8 +21,6 @@ import GhosteryFeature from './GhosteryFeature'; import NotScanned from './NotScanned'; import PauseButton from './PauseButton'; import ToggleSlider from './ToggleSlider'; -import RadioButtonGroup from './RadioButtonGroup'; -import RadioButton from './RadioButton'; import ModalExitButton from './ModalExitButton'; export { @@ -33,7 +31,5 @@ export { NotScanned, PauseButton, ToggleSlider, - RadioButtonGroup, - RadioButton, ModalExitButton }; diff --git a/app/panel/components/Header.jsx b/app/panel/components/Header.jsx index f20bb7ac5..81bc35bf3 100644 --- a/app/panel/components/Header.jsx +++ b/app/panel/components/Header.jsx @@ -15,7 +15,7 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { ReactSVG } from 'react-svg'; import ClassNames from 'classnames'; -import Tooltip from './Tooltip'; +import Tooltip from '../../shared-components/Tooltip'; import HeaderMenu from './HeaderMenu'; import { sendMessage, sendMessageInPromise } from '../utils/msg'; import { log } from '../../../src/utils/common'; diff --git a/app/panel/components/Settings/AdBlocker.jsx b/app/panel/components/Settings/AdBlocker.jsx index 2a5466602..dff505050 100644 --- a/app/panel/components/Settings/AdBlocker.jsx +++ b/app/panel/components/Settings/AdBlocker.jsx @@ -13,7 +13,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { RadioButtonGroup } from '../BuildingBlocks'; +import RadioButtonGroup from '../../../shared-components/RadioButtonGroup/RadioButtonGroup'; /** * @class Implement Ad Blocker Settings subview as a React component. diff --git a/app/panel/components/Subscription/SubscriptionThemes.jsx b/app/panel/components/Subscription/SubscriptionThemes.jsx index f486277d6..72087f438 100644 --- a/app/panel/components/Subscription/SubscriptionThemes.jsx +++ b/app/panel/components/Subscription/SubscriptionThemes.jsx @@ -13,7 +13,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { RadioButtonGroup } from '../BuildingBlocks'; +import RadioButtonGroup from '../../../shared-components/RadioButtonGroup/RadioButtonGroup'; /** * @class Implement Themes subview as a React component. diff --git a/app/panel/components/Summary.jsx b/app/panel/components/Summary.jsx index f89f64d0e..bd56b9018 100644 --- a/app/panel/components/Summary.jsx +++ b/app/panel/components/Summary.jsx @@ -14,7 +14,7 @@ import React from 'react'; import { ReactSVG } from 'react-svg'; import ClassNames from 'classnames'; -import Tooltip from './Tooltip'; +import Tooltip from '../../shared-components/Tooltip'; import DynamicUIPortContext from '../contexts/DynamicUIPortContext'; import { sendMessage } from '../utils/msg'; import globals from '../../../src/classes/Globals'; diff --git a/app/panel/utils/utils.js b/app/panel/utils/utils.js index ce438df33..632d2e1ac 100644 --- a/app/panel/utils/utils.js +++ b/app/panel/utils/utils.js @@ -165,6 +165,20 @@ export function validatePassword(pwd) { return pwd !== '' && pwdRegex.test(pwd); } +/** + * Check for confirm password equality to password + * @memberOf PanelUtils + * @param {string} password password + * @param {string} confirmPassword confirm password to validate + * @return {boolean} true if equal, false otherwise + */ +export function validatePasswordsMatch(password, confirmPassword) { + if (!password || !confirmPassword) { + return false; + } + return password === confirmPassword; +} + /** * Helper method for making XHR requests * @memberOf PanelUtils diff --git a/app/scss/dawn_hub.scss b/app/scss/dawn_hub.scss new file mode 100644 index 000000000..9efd2446a --- /dev/null +++ b/app/scss/dawn_hub.scss @@ -0,0 +1,85 @@ +/** + * Ghostery-Browser-Specific Hub Sass + * + * 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 Global Partials +@import './partials/colors'; + +html, body, #root { + height: 100%; + width: 100%; + margin: 0; +} + +.App { + position: fixed; + z-index: 0; +} +.App__mainContent { + overflow-y: auto; + // Very small windows/phone screens in Firefox slightly overflows on the x-axis + overflow-x: hidden; +} +@media only screen and (max-width: 740px) { + .android-relative {position: relative;} +} + +// Foundation Overrides +.button { + font-size: 14px; + font-weight: 700; + line-height: 1.3; + text-transform: uppercase; + box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 0 2px 0 rgba(0, 0, 0, 0.12); + &.primary { + &:not(.hollow):hover { + background-color: $dark-ghosty-blue; + } + &.hollow:hover, &.hollow:focus { + border-color: $dark-ghosty-blue; + color: $dark-ghosty-blue; + } + } +} + +// Helper Classes +.clickable { cursor: pointer; } +.full-height { height: 100%; } +.full-width { width: 100%; } +.display-inline { display: inline-block; } +.flex-shrink-none { flex-shrink: 0; } + +@import 'settings_hub'; // Import Foundation +@import './partials/_hub_mixins'; +@import './partials/_hub_svgs'; +@import './partials/_shared_components_svgs'; +@import './partials/_fonts'; +@import './partials/_loader'; +@import './partials/radio_button'; + +// Imports from ../ghostery-browser-hub directory +@import '../dawn-hub/Views/OnboardingView/OnboardingView.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.scss'; +@import '../dawn-hub/Views/OnboardingViews/StepProgressbar/StepProgressbar.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step2_BlockSettingsView/BlockSettingsView.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step3_ChooseDefaultSearchView/ChooseDefaultSearchView.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step4_ChoosePlanView/ChoosePlanView.scss'; +@import '../dawn-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.scss'; + +// Imports from ../shared-components directory +@import '../shared-components/ToastMessage/ToastMessage.scss'; +@import '../shared-components/Modal/Modal.scss'; +@import '../shared-components/ToggleCheckbox/ToggleCheckbox.scss'; +@import '../shared-components/Tooltip/Tooltip.scss'; diff --git a/app/scss/hub.scss b/app/scss/hub.scss index 759692615..a085ae814 100644 --- a/app/scss/hub.scss +++ b/app/scss/hub.scss @@ -96,3 +96,4 @@ html, body, #root { @import '../shared-components/ToggleCheckbox/ToggleCheckbox.scss'; @import '../shared-components/ToggleSwitch/ToggleSwitch.scss'; @import '../shared-components/ForgotPassword/ForgotPassword.scss'; +@import './partials/radio_button'; diff --git a/app/scss/panel.scss b/app/scss/panel.scss index a073a33dc..839e8ff3e 100644 --- a/app/scss/panel.scss +++ b/app/scss/panel.scss @@ -69,7 +69,6 @@ html body { @import './partials/_donut_graph'; @import './partials/_ghostery_feature'; @import './partials/_cliqz_feature'; -@import './partials/_tooltip'; @import './partials/_not_scanned'; @import './partials/_subscribe'; @import './partials/_stats'; @@ -82,4 +81,5 @@ html body { @import '../shared-components/ModalContent/PremiumPromoModalContent/PremiumPromoModalContent.scss'; @import '../shared-components/ModalContent/PlusPromoModalContent/PlusPromoModalContent.scss'; @import '../shared-components/ModalContent/InsightsPromoModalContent/InsightsPromoModalContent.scss'; -@import '../shared-components/ForgotPassword/ForgotPassword.scss' +@import '../shared-components/ForgotPassword/ForgotPassword.scss'; +@import '../shared-components/Tooltip/Tooltip.scss'; diff --git a/app/scss/partials/_colors.scss b/app/scss/partials/_colors.scss index cd15e484b..d9d5cbde2 100644 --- a/app/scss/partials/_colors.scss +++ b/app/scss/partials/_colors.scss @@ -49,8 +49,8 @@ $spring-green: #6aa103; /* MARKETING COLORS */ $red: #E74055; $purple: #720174; -$dark-purple: #5B005C; -$dark-magenta: #920094; +$dark-purple: #5B005C; +$dark-magenta: #920094; $sinopia: #D3451E; $apple: #67A73A; //button_special $caper: #CEECAF; //success-color @@ -67,3 +67,14 @@ $transparent-red: rgba($red, 0.08); /* ADD PROJECT COLORS HERE */ $gold: #F8E71C; $bright-gold: #FF9D00; + + +/* UPGRADE PLAN VIEW COLORS */ +$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; diff --git a/app/scss/partials/_radio_button.scss b/app/scss/partials/_radio_button.scss index 44a33a3fd..7675d62e6 100644 --- a/app/scss/partials/_radio_button.scss +++ b/app/scss/partials/_radio_button.scss @@ -40,7 +40,17 @@ align-items: center; justify-content: center; box-sizing: border-box; - &.checked { + + &.altDesign { + height: 24px; + width: 24px; + border: 1px solid black; + &.checked { + background-color: #00aef0; + border: none; + } + } + &.checked:not(.altDesign) { border: 2px solid #2092bf; } } @@ -51,4 +61,14 @@ background-color: #2092bf; border-radius: 50%; } + + &.altDesign { + height: 14px; + width: 14px; + background-image: buildCheckIcon(#FFF); + border: none; + &.checked { + background-color: transparent; + } + } } diff --git a/app/panel/components/BuildingBlocks/RadioButton.jsx b/app/shared-components/RadioButton/RadioButton.jsx similarity index 93% rename from app/panel/components/BuildingBlocks/RadioButton.jsx rename to app/shared-components/RadioButton/RadioButton.jsx index 12924cfb3..63cfba0fe 100644 --- a/app/panel/components/BuildingBlocks/RadioButton.jsx +++ b/app/shared-components/RadioButton/RadioButton.jsx @@ -21,12 +21,14 @@ import ClassNames from 'classnames'; */ const RadioButton = (props) => { - const { checked, handleClick } = props; + const { checked, handleClick, altDesign } = props; const OuterCircleClassNames = ClassNames('RadioButton__outerCircle', { checked, + altDesign }); const InnerCircleClassNames = ClassNames('RadioButton__innerCircle', { checked, + altDesign }); return ( diff --git a/app/shared-components/RadioButton/index.js b/app/shared-components/RadioButton/index.js new file mode 100644 index 000000000..a4160127a --- /dev/null +++ b/app/shared-components/RadioButton/index.js @@ -0,0 +1,16 @@ +/** + * Point of entry index.js file for Radio Button + * + * 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 RadioButton from './RadioButton'; + +export default RadioButton; diff --git a/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx b/app/shared-components/RadioButtonGroup/RadioButtonGroup.jsx similarity index 97% rename from app/panel/components/BuildingBlocks/RadioButtonGroup.jsx rename to app/shared-components/RadioButtonGroup/RadioButtonGroup.jsx index 2722ceddc..a91d4b8f4 100644 --- a/app/panel/components/BuildingBlocks/RadioButtonGroup.jsx +++ b/app/shared-components/RadioButtonGroup/RadioButtonGroup.jsx @@ -13,7 +13,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import RadioButton from './RadioButton'; +import RadioButton from '../RadioButton'; /** * @class Implements a radio button group diff --git a/app/shared-components/RadioButtonGroup/index.js b/app/shared-components/RadioButtonGroup/index.js new file mode 100644 index 000000000..a7d9aa0d8 --- /dev/null +++ b/app/shared-components/RadioButtonGroup/index.js @@ -0,0 +1,16 @@ +/** + * Point of entry index.js file for Radio Button Group + * + * 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 RadioButtonGroup from './RadioButtonGroup'; + +export default RadioButtonGroup; diff --git a/app/shared-components/ToastMessage/ToastMessage.jsx b/app/shared-components/ToastMessage/ToastMessage.jsx index 2cd45cc12..95a092ac7 100644 --- a/app/shared-components/ToastMessage/ToastMessage.jsx +++ b/app/shared-components/ToastMessage/ToastMessage.jsx @@ -19,24 +19,40 @@ import PropTypes from 'prop-types'; * @return {JSX} JSX for rendering a Toast Message * @memberof SharedComponents */ -const ToastMessage = ({ toastText, toastClass, toastExit }) => ( -
- {toastText && ( -
-
-
-
- {toastText} +const ToastMessage = ({ + toastText, toastClass, toastExit, dawnHub +}) => { + // These variables will be used to determine whether the toast should display with Dawn + // onboarding styling or the default styling used in GBE + const dawnHubClass = dawnHub ? 'dawn-hub' : ''; + const dawnLayout = dawnHub ? 'align-justify align-middle' : 'align-center-middle'; + + const dawnToastText = dawnHub ? t(`ghostery_dawn_onboarding_toast_${toastClass}`) : ''; + + return ( +
+ {toastText && ( +
+
+
+
+ {dawnHub && ( + + )} +
+ {`${dawnToastText}${toastText}`} +
+
+ {toastExit && ( +
+ )}
- {toastExit && ( -
- )}
-
- )} -
-); + )} +
+ ); +}; // PropTypes ensure we pass required props of the correct type ToastMessage.propTypes = { @@ -46,11 +62,13 @@ ToastMessage.propTypes = { PropTypes.func, PropTypes.bool, ]), + dawnHub: PropTypes.bool, }; // Default props used in the Toast Message ToastMessage.defaultProps = { toastExit: false, + dawnHub: false, }; export default ToastMessage; diff --git a/app/shared-components/ToastMessage/ToastMessage.scss b/app/shared-components/ToastMessage/ToastMessage.scss index 4040110fa..81faf55fc 100644 --- a/app/shared-components/ToastMessage/ToastMessage.scss +++ b/app/shared-components/ToastMessage/ToastMessage.scss @@ -16,7 +16,32 @@ position: relative; top: 30px; height: 0; + z-index: 30; + &.dawn-hub { + z-index: 1; + max-width: 700px; + margin: auto; + font-family: Roboto; + font-size: 14px; + font-weight: 500; + .alert { + background-color: #f8e6e6; + .callout-text {color: #9f4948;} + } + .success { + background-color: #dff0d8; + .callout-text {color: #417505;} + } + .ToastMessage__close { + background-image: buildIconX(#000000); + } + } } + +.ToastMessage_classIcon { + margin-right: 20px; +} + .ToastMessage__close { height: 13px; width: 13px; diff --git a/app/shared-components/ToastMessage/__tests__/__snapshots__/ToastMessage.test.jsx.snap b/app/shared-components/ToastMessage/__tests__/__snapshots__/ToastMessage.test.jsx.snap index 0ab06d344..1a5c0fba2 100644 --- a/app/shared-components/ToastMessage/__tests__/__snapshots__/ToastMessage.test.jsx.snap +++ b/app/shared-components/ToastMessage/__tests__/__snapshots__/ToastMessage.test.jsx.snap @@ -2,7 +2,7 @@ exports[`app/shared-components/ToastMessage More Snapshot tests with react-test-renderer, but for edge cases edge case where text is empty string 1`] = `
@@ -10,7 +10,7 @@ exports[`app/shared-components/ToastMessage More Snapshot tests with react-test- exports[`app/shared-components/ToastMessage Snapshot tests with react-test-renderer toast message is rendered correctly 1`] = `
- sample text +
+ sample text +
@@ -34,7 +38,7 @@ exports[`app/shared-components/ToastMessage Snapshot tests with react-test-rende exports[`app/shared-components/ToastMessage Snapshot tests with react-test-renderer toast message is rendered correctly with a close button 1`] = `
- example text +
+ example text +
makeDeferredDispatcher(SET_ANTI_TRACKING, actionData); + +export const setAdBlock = + actionData => makeDeferredDispatcher(SET_AD_BLOCK, actionData); + +export const setSmartBlocking = + actionData => makeDeferredDispatcher(SET_SMART_BLOCK, actionData); diff --git a/app/shared-hub/actions/BlockingPolicyActions.js b/app/shared-hub/actions/BlockingPolicyActions.js new file mode 100644 index 000000000..15f19c257 --- /dev/null +++ b/app/shared-hub/actions/BlockingPolicyActions.js @@ -0,0 +1,20 @@ +/** + * Blocking Policy Action Creators for the Hubs to use + * + * 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 { makeDeferredDispatcher } from '../utils'; +import { SET_BLOCKING_POLICY } from '../constants/BlockingPolicyConstants'; + +const setBlockingPolicy = + actionData => makeDeferredDispatcher(SET_BLOCKING_POLICY, actionData); + +export default setBlockingPolicy; diff --git a/app/shared-hub/actions/MetricsActions.js b/app/shared-hub/actions/MetricsActions.js new file mode 100644 index 000000000..c2925045c --- /dev/null +++ b/app/shared-hub/actions/MetricsActions.js @@ -0,0 +1,7 @@ +import { makeDeferredDispatcher } from '../utils'; +import SEND_PING from '../constants/MetricsConstants'; + +const sendPing = + actionData => makeDeferredDispatcher(SEND_PING, actionData); + +export default sendPing; diff --git a/app/shared-hub/actions/SetupLifecycleActions.js b/app/shared-hub/actions/SetupLifecycleActions.js new file mode 100644 index 000000000..3f40698e9 --- /dev/null +++ b/app/shared-hub/actions/SetupLifecycleActions.js @@ -0,0 +1,32 @@ +/** + * Setup Lifecycle Actions for the Hubs + * + * 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 { makeDeferredDispatcher } from '../utils'; +import { + INIT_SETUP_PROPS, + SET_SETUP_STEP, + SET_SETUP_COMPLETE +} from '../constants/SetupLifecycleConstants'; + +export function initSetupProps(data) { + return { + type: INIT_SETUP_PROPS, + data, + }; +} + +export const setSetupStep = + actionData => makeDeferredDispatcher(SET_SETUP_STEP, actionData); + +export const setSetupComplete = + actionData => makeDeferredDispatcher(SET_SETUP_COMPLETE, actionData); diff --git a/app/shared-hub/actions/ToastActions.js b/app/shared-hub/actions/ToastActions.js new file mode 100644 index 000000000..7fa065c3c --- /dev/null +++ b/app/shared-hub/actions/ToastActions.js @@ -0,0 +1,18 @@ +/** + * Toast 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 SET_TOAST from '../constants/ToastConstants'; + +const setToast = data => ({ type: SET_TOAST, data }); + +export default setToast; diff --git a/app/shared-hub/actions/__tests__/AntiSuiteActions.test.js b/app/shared-hub/actions/__tests__/AntiSuiteActions.test.js new file mode 100644 index 000000000..f61816760 --- /dev/null +++ b/app/shared-hub/actions/__tests__/AntiSuiteActions.test.js @@ -0,0 +1,88 @@ +/** + * Test file for Anti Suite Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as utils from '../../utils'; +import * as AntiSuiteActions from '../AntiSuiteActions'; +import { SET_AD_BLOCK, SET_ANTI_TRACKING, SET_SMART_BLOCK } from '../../constants/AntiSuiteConstants'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; + +const mockSendMessageInPromise = jest.fn((dispatch, name, message) => new Promise((resolve, reject) => { + dispatch({ + type: name, + testData + }); + switch (name) { + case 'SET_ANTI_TRACKING': { + resolve(testData); + break; + } + case 'SET_AD_BLOCK': { + resolve(testData); + break; + } + case 'SET_SMART_BLOCK': { + resolve(testData); + break; + } + default: resolve(testData); + } +})); + +utils.sendMessageInPromise = mockSendMessageInPromise; + +utils.makeDeferredDispatcher = jest.fn((action, actionData) => dispatch => { + return mockSendMessageInPromise(dispatch, action, actionData); +}) + +describe('app/shared-hub/actions/AntiSuiteActions', () => { + test('setAdBlock action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const expectedPayload = { testData, type: SET_AD_BLOCK }; + return store.dispatch(AntiSuiteActions.setAdBlock()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); + + test('setAntiTracking action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { testData, type: SET_ANTI_TRACKING }; + return store.dispatch(AntiSuiteActions.setAntiTracking()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); + + test('setSmartBlock action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { testData, type: SET_SMART_BLOCK }; + return store.dispatch(AntiSuiteActions.setSmartBlocking()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); +}); diff --git a/app/shared-hub/actions/__tests__/BlockingPolicyActions.test.js b/app/shared-hub/actions/__tests__/BlockingPolicyActions.test.js new file mode 100644 index 000000000..a99af2443 --- /dev/null +++ b/app/shared-hub/actions/__tests__/BlockingPolicyActions.test.js @@ -0,0 +1,56 @@ +/** + * Test file for Blocking Policy Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as utils from '../../utils'; +import setBlockingPolicy from '../BlockingPolicyActions'; +import { SET_BLOCKING_POLICY } from '../../constants/BlockingPolicyConstants' + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; + +const mockSendMessageInPromise = jest.fn((dispatch, name, message) => new Promise((resolve, reject) => { + dispatch({ + type: name, + testData + }); + switch (name) { + case 'SET_BLOCKING_POLICY': { + resolve(testData); + break; + } + default: resolve(testData); + } +})); + +utils.sendMessageInPromise = mockSendMessageInPromise; + +utils.makeDeferredDispatcher = jest.fn((action, actionData) => dispatch => { + return mockSendMessageInPromise(dispatch, action, actionData); +}) + +describe('app/shared-hub/actions/BlockingPolicyActions', () => { + test('setBlockingPolicy action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const expectedPayload = { testData, type: SET_BLOCKING_POLICY }; + return store.dispatch(setBlockingPolicy()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); +}); diff --git a/app/shared-hub/actions/__tests__/MetricsActions.test.js b/app/shared-hub/actions/__tests__/MetricsActions.test.js new file mode 100644 index 000000000..9dc24db65 --- /dev/null +++ b/app/shared-hub/actions/__tests__/MetricsActions.test.js @@ -0,0 +1,56 @@ +/** + * Test file for Metrics Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as utils from '../../utils'; +import sendPing from '../MetricsActions'; +import SEND_PING from '../../constants/MetricsConstants' + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; + +const mockSendMessageInPromise = jest.fn((dispatch, name, message) => new Promise((resolve, reject) => { + dispatch({ + type: name, + testData + }); + switch (name) { + case 'SEND_PING': { + resolve(testData); + break; + } + default: resolve(testData); + } +})); + +utils.sendMessageInPromise = mockSendMessageInPromise; + +utils.makeDeferredDispatcher = jest.fn((action, actionData) => dispatch => { + return mockSendMessageInPromise(dispatch, action, actionData); +}) + +describe('app/shared-hub/actions/MetricsActions', () => { + test('sendPing action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const expectedPayload = { testData, type: SEND_PING }; + return store.dispatch(sendPing()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); +}); diff --git a/app/shared-hub/actions/__tests__/SetupLifecycleActions.test.js b/app/shared-hub/actions/__tests__/SetupLifecycleActions.test.js new file mode 100644 index 000000000..18a780804 --- /dev/null +++ b/app/shared-hub/actions/__tests__/SetupLifecycleActions.test.js @@ -0,0 +1,89 @@ +/** + * Test file for Setup Lifecycle Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import * as utils from '../../utils'; +import * as SetupLifecycleActions from '../SetupLifecycleActions'; +import { INIT_SETUP_PROPS, SET_SETUP_STEP, SET_SETUP_COMPLETE } from '../../constants/SetupLifecycleConstants'; + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; + +const mockSendMessageInPromise = jest.fn((dispatch, name, message) => new Promise((resolve, reject) => { + dispatch({ + type: name, + testData + }); + switch (name) { + case 'INIT_SETUP_PROPS': { + resolve(testData); + break; + } + case 'SET_SETUP_STEP': { + resolve(testData); + break; + } + case 'SET_SETUP_COMPLETE': { + resolve(testData); + break; + } + default: resolve(testData); + } +})); + +utils.sendMessageInPromise = mockSendMessageInPromise; + +utils.makeDeferredDispatcher = jest.fn((action, actionData) => dispatch => { + return mockSendMessageInPromise(dispatch, action, actionData); +}) + + +describe('app/shared-hub/actions/AntiSuiteActions', () => { + test('initSetupProps action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { data, type: INIT_SETUP_PROPS }; + store.dispatch(SetupLifecycleActions.initSetupProps(testData)); + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + + test('setSetupStep action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { testData, type: SET_SETUP_STEP }; + return store.dispatch(SetupLifecycleActions.setSetupStep()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); + + test('setSetupStep action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { testData, type: SET_SETUP_COMPLETE }; + return store.dispatch(SetupLifecycleActions.setSetupComplete()).then(() => { + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); + }); +}); diff --git a/app/shared-hub/actions/__tests__/ToastActions.test.js b/app/shared-hub/actions/__tests__/ToastActions.test.js new file mode 100644 index 000000000..bed3b286c --- /dev/null +++ b/app/shared-hub/actions/__tests__/ToastActions.test.js @@ -0,0 +1,35 @@ +/** + * Test file for Toast Actions + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 configureStore from 'redux-mock-store'; +import thunk from 'redux-thunk'; +import setToast from '../ToastActions'; +import SET_TOAST from '../../constants/ToastConstants' + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); + +const testData = { test: true }; + +describe('app/shared-hub/actions/ToastActions', () => { + test('setToast action should return correctly', () => { + const initialState = {}; + const store = mockStore(initialState); + + const data = testData; + const expectedPayload = { data, type: SET_TOAST }; + store.dispatch(setToast(testData)); + const actions = store.getActions(); + expect(actions).toEqual([expectedPayload]); + }); +}); diff --git a/app/shared-hub/constants/AntiSuiteConstants.js b/app/shared-hub/constants/AntiSuiteConstants.js new file mode 100644 index 000000000..23803a225 --- /dev/null +++ b/app/shared-hub/constants/AntiSuiteConstants.js @@ -0,0 +1,16 @@ +/** + * Anti Suite Constants for use by both Hubs + * + * 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 + */ + +export const SET_ANTI_TRACKING = 'SET_ANTI_TRACKING'; +export const SET_AD_BLOCK = 'SET_AD_BLOCK'; +export const SET_SMART_BLOCK = 'SET_SMART_BLOCK'; diff --git a/app/shared-hub/constants/BlockingPolicyConstants.js b/app/shared-hub/constants/BlockingPolicyConstants.js new file mode 100644 index 000000000..5f51aae43 --- /dev/null +++ b/app/shared-hub/constants/BlockingPolicyConstants.js @@ -0,0 +1,18 @@ +/** + * Blocking Policy Constants Used By The Hubs + * + * 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 + */ + +export const SET_BLOCKING_POLICY = 'SET_BLOCKING_POLICY'; +export const BLOCKING_POLICY_RECOMMENDED = 'BLOCKING_POLICY_RECOMMENDED'; +export const BLOCKING_POLICY_NOTHING = 'BLOCKING_POLICY_NOTHING'; +export const BLOCKING_POLICY_EVERYTHING = 'BLOCKING_POLICY_EVERYTHING'; +export const BLOCKING_POLICY_CUSTOM = 'BLOCKING_POLICY_CUSTOM'; diff --git a/app/shared-hub/constants/MetricsConstants.js b/app/shared-hub/constants/MetricsConstants.js new file mode 100644 index 000000000..9c53ddb8d --- /dev/null +++ b/app/shared-hub/constants/MetricsConstants.js @@ -0,0 +1,16 @@ +/** + * Shared Hub Metrics 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 + */ + +const SEND_PING = 'SEND_PING'; + +export default SEND_PING; diff --git a/app/shared-hub/constants/SetupLifecycleConstants.js b/app/shared-hub/constants/SetupLifecycleConstants.js new file mode 100644 index 000000000..97c968d3e --- /dev/null +++ b/app/shared-hub/constants/SetupLifecycleConstants.js @@ -0,0 +1,16 @@ +/** + * Setup Lifecycle Constants for the Hubs + * + * 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 + */ + +export const INIT_SETUP_PROPS = 'INIT_SETUP_PROPS'; +export const SET_SETUP_STEP = 'SET_SETUP_STEP'; +export const SET_SETUP_COMPLETE = 'SET_SETUP_COMPLETE'; diff --git a/app/shared-hub/constants/ToastConstants.js b/app/shared-hub/constants/ToastConstants.js new file mode 100644 index 000000000..92b33ee86 --- /dev/null +++ b/app/shared-hub/constants/ToastConstants.js @@ -0,0 +1,16 @@ +/** + * Toast Constants Used By The Hubs + * + * 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 + */ + +const SET_TOAST = 'SET_TOAST'; + +export default SET_TOAST; diff --git a/app/shared-hub/reducers/AntiSuiteReducer.js b/app/shared-hub/reducers/AntiSuiteReducer.js new file mode 100644 index 000000000..458ce5558 --- /dev/null +++ b/app/shared-hub/reducers/AntiSuiteReducer.js @@ -0,0 +1,41 @@ +/** + * Anti Suite reducer for the Hubs + * + * 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 { + SET_AD_BLOCK, + SET_ANTI_TRACKING, + SET_SMART_BLOCK +} from '../constants/AntiSuiteConstants'; + +const initialState = {}; + +function AntiSuiteReducer(state = initialState, action) { + switch (action.type) { + case SET_AD_BLOCK: { + const { enable_ad_block } = action.data; + return { ...state, setup: { ...state.setup, enable_ad_block } }; + } + case SET_ANTI_TRACKING: { + const { enable_anti_tracking } = action.data; + return { ...state, setup: { ...state.setup, enable_anti_tracking } }; + } + case SET_SMART_BLOCK: { + const { enable_smart_block } = action.data; + return { ...state, setup: { ...state.setup, enable_smart_block } }; + } + + default: return state; + } +} + +export default AntiSuiteReducer; diff --git a/app/shared-hub/reducers/BlockingPolicyReducer.js b/app/shared-hub/reducers/BlockingPolicyReducer.js new file mode 100644 index 000000000..81e677584 --- /dev/null +++ b/app/shared-hub/reducers/BlockingPolicyReducer.js @@ -0,0 +1,29 @@ +/** + * Reducer used throughout the Onboarding View'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 { SET_BLOCKING_POLICY } from '../constants/BlockingPolicyConstants'; + +const initialState = {}; + +function BlockingPolicyReducer(state = initialState, action) { + switch (action.type) { + case SET_BLOCKING_POLICY: { + const { blockingPolicy } = action.data; + return { ...state, setup: { ...state.setup, blockingPolicy } }; + } + + default: return state; + } +} + +export default BlockingPolicyReducer; diff --git a/app/shared-hub/reducers/SetupLifecycleReducer.js b/app/shared-hub/reducers/SetupLifecycleReducer.js new file mode 100644 index 000000000..3d30ac6c8 --- /dev/null +++ b/app/shared-hub/reducers/SetupLifecycleReducer.js @@ -0,0 +1,45 @@ +/** + * Reducer for the setup flow's lifecycle events, for use by the Hubs + * + * 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 { + INIT_SETUP_PROPS +} from '../constants/SetupLifecycleConstants'; + +const initialState = {}; + +function SetupLifecycleReducer(state = initialState, action) { + switch (action.type) { + // TODO add navigation, override warning, and human web props init as separate case to share this with original hub + case INIT_SETUP_PROPS: { + const { + blockingPolicy, + enable_anti_tracking, + enable_ad_block, + enable_smart_block, + } = action.data; + return { + ...state, + setup: { + blockingPolicy, + enable_anti_tracking, + enable_ad_block, + enable_smart_block, + } + }; + } + + default: return state; + } +} + +export default SetupLifecycleReducer; diff --git a/app/shared-hub/reducers/ToastReducer.js b/app/shared-hub/reducers/ToastReducer.js new file mode 100644 index 000000000..d602416a8 --- /dev/null +++ b/app/shared-hub/reducers/ToastReducer.js @@ -0,0 +1,34 @@ +/** + * Toast reducer used by the Hubs + * + * 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 SET_TOAST from '../constants/ToastConstants'; + +const initialState = {}; + +function ToastReducer(state = initialState, action) { + switch (action.type) { + case SET_TOAST: { + const { toastMessage, toastClass } = action.data; + return { + ...state, + toast: { + toastMessage, + toastClass, + }, + }; + } + default: return state; + } +} + +export default ToastReducer; diff --git a/app/shared-hub/reducers/__tests__/AntiSuiteReducer.test.js b/app/shared-hub/reducers/__tests__/AntiSuiteReducer.test.js new file mode 100644 index 000000000..3bd25668f --- /dev/null +++ b/app/shared-hub/reducers/__tests__/AntiSuiteReducer.test.js @@ -0,0 +1,69 @@ +/** + * AntiSuite Test Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 Immutable from 'seamless-immutable'; +import AntiSuiteReducer from '../AntiSuiteReducer'; +import { SET_AD_BLOCK, SET_ANTI_TRACKING, SET_SMART_BLOCK } from '../../constants/AntiSuiteConstants'; + +const initialState = Immutable({ + setup: { + enable_ad_block: false, + enable_anti_tracking: false, + enable_smart_block: false + } +}); + +describe('app/shared-hub/reducers/AntiSuiteReducer', () => { + test('initial state is correct', () => { + expect(AntiSuiteReducer(undefined, {})).toEqual({}); + }); + + test('reducer correctly handles SET_AD_BLOCK', () => { + const data = { + enable_ad_block: true, + }; + const action = { data, type: SET_AD_BLOCK }; + + const updatedAntiSuiteState = Immutable.merge(initialState.setup, data); + + expect(AntiSuiteReducer(initialState, action)).toEqual({ + setup: updatedAntiSuiteState + }); + }); + + test('reducer correctly handles SET_ANTI_TRACKING', () => { + const data = { + enable_anti_tracking: true, + }; + const action = { data, type: SET_ANTI_TRACKING }; + + const updatedAntiSuiteState = Immutable.merge(initialState.setup, data); + + expect(AntiSuiteReducer(initialState, action)).toEqual({ + setup: updatedAntiSuiteState + }); + }); + + test('reducer correctly handles SET_SMART_BLOCK', () => { + const data = { + enable_smart_block: true, + }; + const action = { data, type: SET_SMART_BLOCK }; + + const updatedAntiSuiteState = Immutable.merge(initialState.setup, data); + + expect(AntiSuiteReducer(initialState, action)).toEqual({ + setup: updatedAntiSuiteState + }); + }); +}); diff --git a/app/shared-hub/reducers/__tests__/BlockingPolicyReducer.test.js b/app/shared-hub/reducers/__tests__/BlockingPolicyReducer.test.js new file mode 100644 index 000000000..b97efc8b1 --- /dev/null +++ b/app/shared-hub/reducers/__tests__/BlockingPolicyReducer.test.js @@ -0,0 +1,41 @@ +/** + * BlockingPolicy Test Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 Immutable from 'seamless-immutable'; +import BlockingPolicyReducer from '../BlockingPolicyReducer'; +import { SET_BLOCKING_POLICY } from '../../constants/BlockingPolicyConstants'; + +const initialState = Immutable({ + setup: { + blockingPolicy: true + } +}); + +describe('app/shared-hub/reducers/BlockingPolicy', () => { + test('initial state is correct', () => { + expect(BlockingPolicyReducer(undefined, {})).toEqual({}); + }); + + test('reducer correctly handles SET_BLOCKING_POLICY', () => { + const data = { + blockingPolicy: true, + }; + const action = { data, type: SET_BLOCKING_POLICY }; + + const updatedBlockingPolicyState = Immutable.merge(initialState.setup, data); + + expect(BlockingPolicyReducer(initialState, action)).toEqual({ + setup: updatedBlockingPolicyState + }); + }); +}); diff --git a/app/shared-hub/reducers/__tests__/SetupLifeCycleReducer.test.js b/app/shared-hub/reducers/__tests__/SetupLifeCycleReducer.test.js new file mode 100644 index 000000000..49659a6c1 --- /dev/null +++ b/app/shared-hub/reducers/__tests__/SetupLifeCycleReducer.test.js @@ -0,0 +1,42 @@ +/** + * SetupLifecycle Test Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 Immutable from 'seamless-immutable'; +import SetupLifecycleReducer from '../SetupLifecycleReducer'; +import { INIT_SETUP_PROPS } from '../../constants/SetupLifecycleConstants'; + +const initialState = Immutable({ + setup: {} +}); + +describe('app/shared-hub/reducers/SetupLifecycleReducer', () => { + test('initial state is correct', () => { + expect(SetupLifecycleReducer(undefined, {})).toEqual({}); + }); + + test('reducer correctly handles INIT_SETUP_PROPS', () => { + const data = { + blockingPolicy: true, + enable_anti_tracking: true, + enable_ad_block: true, + enable_smart_block: true, + }; + const action = { data, type: INIT_SETUP_PROPS }; + + const updatedSetupLifecycleState = Immutable.merge(initialState.setup, data); + + expect(SetupLifecycleReducer(initialState, action)).toEqual({ + setup: updatedSetupLifecycleState + }); + }); +}); diff --git a/app/shared-hub/reducers/__tests__/ToastReducer.test.js b/app/shared-hub/reducers/__tests__/ToastReducer.test.js new file mode 100644 index 000000000..e0808d3f1 --- /dev/null +++ b/app/shared-hub/reducers/__tests__/ToastReducer.test.js @@ -0,0 +1,39 @@ +/** + * Toast Test Reducer + * + * Ghostery Browser Extension + * https://www.ghostery.com/ + * + * Copyright 2021 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 Immutable from 'seamless-immutable'; +import ToastReducer from '../ToastReducer'; +import SET_TOAST from '../../constants/ToastConstants'; + +const initialState = Immutable({ + toast: {} +}); + +describe('app/shared-hub/reducers/ToastReducer', () => { + test('initial state is correct', () => { + expect(ToastReducer(undefined, {})).toEqual({}); + }); + + test('reducer correctly handles SET_TOAST', () => { + const data = { + toastMessage: 'Toaster', + toastClass: 'danger' + }; + const action = { data, type: SET_TOAST }; + + const updatedToastReducerState = Immutable.merge(initialState.toast, data); + expect(ToastReducer(initialState, action)).toEqual({ + toast: updatedToastReducerState + }); + }); +}); diff --git a/app/shared-hub/utils/index.js b/app/shared-hub/utils/index.js new file mode 100644 index 000000000..601e9eb22 --- /dev/null +++ b/app/shared-hub/utils/index.js @@ -0,0 +1,119 @@ +/** + * React Utility Imports + * Import files from app to prevent duplicate code + * + * 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 { + applyMiddleware, + compose, + combineReducers, + createStore, + bindActionCreators +} from 'redux'; +import { connect } from 'react-redux'; + +// Imports utilities from elsewhere in the codebase to reduce duplicate code +import { log } from '../../../src/utils/common'; +import { sendMessage as importedSM, sendMessageInPromise as importedSMIP } from '../../panel/utils/msg'; + +const sendMessageInPromise = function(name, message) { + return importedSMIP(name, message, 'ghostery-hub'); +}; + +const sendMessage = function(name, message) { + return importedSM(name, message, 'ghostery-hub'); +}; + +/** + * @since 8.5.5 + * + * Create a function that uses redux's createStore to make a store with the supplied reducers and middlewares. + * + * @param {Object} reducers The reducers to combine. + * @param {Array} [middlewares] (Optional) The middlewares to apply, in the order they should be applied. + * + * @return {Function} The function that will create the store when called. + */ +const makeStoreCreator = function(reducers, middlewares) { + const reducer = combineReducers(reducers); + + return () => createStore( + reducer, + compose( + applyMiddleware(...middlewares), + window.devToolsExtension ? window.devToolsExtension() : f => f + ), + ); +}; + +/** + * @since 8.5.5 + * + * Creates and returns a function that sends a Promise-wrapped message to the background, + * then dispatches the supplied action with the resolved data. + * + * @param {String} action The action to dispatch when the Promise resolves. + * @param {*} actionData The data to send to the background. + * + * @returns {Function} Calling the returned function sends a message with the supplied + * action and actionData to the background in a Promise, and dispatches + * the action with the returned data if the Promise resolves. It prints + * an error if the Promise rejects. + */ +function makeDeferredDispatcher(action, actionData) { + return function(dispatch) { + return sendMessageInPromise(action, actionData).then((data) => { + dispatch({ + type: action, + data, + }); + }).catch((err) => { + log(`${action} action creator error`, err); + }); + }; +} + +/** + * @since 8.5.5 + * + * Uses react-redux's connect to wrap the provided base component in a HOC, + * with the provided state slices mapped to props and the provided action creators dispatch mapped to props.actions. + * + * @param {Array|null} stateKeys The slices of the Redux state store you want to map to props. Pass null if none. + * @param {Object|null} actionCreators The action creators you want to dispatch map to props.actions. Pass null if none. + * @param {*} baseComponent The React component you want to wrap. + * + * @returns {*} The created HOC. + * + */ +function buildReduxHOC(stateKeys, actionCreators, baseComponent) { + const mapStateToProps = (stateKeys === null) + ? null + : state => stateKeys.reduce((acc, key) => ({ ...acc, ...state[key] }), {}); + + const mapDispatchToProps = (actionCreators === null) + ? null + : dispatch => ({ + actions: bindActionCreators(actionCreators, dispatch) + }); + + return connect(mapStateToProps, mapDispatchToProps)(baseComponent); +} + +export { + buildReduxHOC, + makeStoreCreator, + log, + sendMessage, + sendMessageInPromise, + makeDeferredDispatcher +}; diff --git a/app/templates/dawn_hub.html b/app/templates/dawn_hub.html new file mode 100644 index 000000000..c9d4474a3 --- /dev/null +++ b/app/templates/dawn_hub.html @@ -0,0 +1,26 @@ + + + + + + + + + + + +
+ + + diff --git a/jsdoc.json b/jsdoc.json index 5eeac976d..70a203ed3 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -7,6 +7,7 @@ "app/licenses", "app/panel", "app/hub", + "app/dawn-hub", "app/shared-components" ], "includePattern": ".+\\.js(doc|x)?$", diff --git a/package.json b/package.json index c75e4fc76..40bcf8dac 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "classnames": "^2.2.5", "d3": "^5.16.0", "foundation-sites": "^6.6.2", - "ghostery-common": "^1.2.0", + "ghostery-common": "^1.2.1", "history": "^4.10.1", "jquery": "3.5.0", "json-api-normalizer": "^1.0.0", diff --git a/src/background.js b/src/background.js index e0b64a216..5c5fce5cb 100644 --- a/src/background.js +++ b/src/background.js @@ -521,6 +521,12 @@ function handleGhosteryHub(name, message, callback) { } else { ({ setup_step } = setup_step); } + const origin = message.origin || ''; + if (origin === 'onboarding') { + conf.setup_step = message.setup_step; + conf.dawn_setup_number = message.dawn_setup_number; + metrics.ping('gb_onboarding'); + } callback({ setup_step }); break; } @@ -952,7 +958,9 @@ function onMessageHandler(request, sender, callback) { return true; } if (name === 'openHubPage') { - const hubUrl = chrome.runtime.getURL('./app/templates/hub.html'); + const hubUrl = (BROWSER_INFO.name === 'ghostery_desktop') + ? chrome.runtime.getURL('./app/templates/dawn_hub.html') + : chrome.runtime.getURL('./app/templates/hub.html'); metrics.ping('intro_hub_click'); utils.openNewTab({ url: hubUrl, become_active: true }); return false; @@ -1608,16 +1616,26 @@ function initializeGhosteryModules() { ]).then(() => { // run scheduledTasks on init scheduledTasks().then(() => { - // Open the Ghostery Hub on install with justInstalled query parameter set to true. - // We need to do this after running scheduledTasks for the first time - // because of an A/B test that determines which promo variant is shown in the Hub on install - if (globals.JUST_INSTALLED && BROWSER_INFO.name !== 'ghostery_desktop') { - const showAlternateHub = conf.hub_layout === 'alternate'; - const route = showAlternateHub ? '#home' : ''; - chrome.tabs.create({ - url: chrome.runtime.getURL(`./app/templates/hub.html?justInstalled=true&ah=${showAlternateHub}${route}`), - active: true - }); + if (globals.JUST_INSTALLED) { + (async() => { + await globals.BROWSER_INFO_READY; + if (BROWSER_INFO.name === 'ghostery_desktop') { // i.e., Dawn + chrome.tabs.create({ + url: chrome.runtime.getURL('./app/templates/dawn_hub.html?justInstalled=true'), + active: true + }); + } else { + // Open the Ghostery Hub on install with justInstalled query parameter set to true. + // We need to do this after running scheduledTasks for the first time + // because of an A/B test that determines which promo variant is shown in the Hub on install + const showAlternateHub = conf.hub_layout === 'alternate'; + const route = showAlternateHub ? '#home' : ''; + chrome.tabs.create({ + url: chrome.runtime.getURL(`./app/templates/hub.html?justInstalled=true&ah=${showAlternateHub}${route}`), + active: true + }); + } + })(); } }); }); diff --git a/src/classes/ConfData.js b/src/classes/ConfData.js index 1136261e3..c12bc6bd7 100644 --- a/src/classes/ConfData.js +++ b/src/classes/ConfData.js @@ -146,6 +146,7 @@ class ConfData { _initProperty('setup_step', 7); _initProperty('setup_show_warning_override', true); _initProperty('setup_number', 0); + _initProperty('dawn_setup_number', '0'); _initProperty('setup_block', 1); _initProperty('setup_complete', false); _initProperty('tutorial_complete', false); diff --git a/src/classes/Debugger.js b/src/classes/Debugger.js index fd5e2c8b5..1ca88c83b 100644 --- a/src/classes/Debugger.js +++ b/src/classes/Debugger.js @@ -237,7 +237,7 @@ class Debugger { [`${this._helpFunctionNames.getConfData}`, 'Show the current value of a config property or properties'], [`${this._helpFunctionNames.getGlobals}`, 'Show the current value of a global property or properties'], [`${this._helpFunctionNames.getUserData}`, 'Show account data for the logged in user and account event history'], - [`${this._helpFunctionNames.openIntroHub}`, 'Open the Ghostery Intro Hub in a new tab for automation testing'], + [`${this._helpFunctionNames.openIntroHub}`, 'Open the classic or Dawn Hub in a new tab for automation testing'], [`${this._helpFunctionNames.openPanel}`, 'Open the Ghostery panel window in a new tab for automation testing'], [`${this._helpFunctionNames.showPromoModal}`, 'Show specified promo modal at the next opportunity'], ] @@ -391,11 +391,12 @@ class Debugger { */ static helpOpenIntroHub = [ `${CSS_MAINHEADER}${this._helpFunctionNames.openIntroHub}`, - 'Open the Ghostery Intro Hub in a new tab for automation testing.', + 'Open the classic or Dawn Hub in a new tab for automation testing', '', - [`${CSS_SUBHEADER}When called with...`, 'Opens...'], - ['No argument', 'The hub on the default route'], - ['modal', 'The hub with any promo modals displayed'], + [`${CSS_SUBHEADER}Arguments`, 'Description'], + ['First: true / false', 'Required. True to open the Dawn Hub. False to open the classic Hub.'], + ['Second: true / false', 'Required. True to simulate first on-install open (justInstalled=true). Second to simulate return visit.'], + ["Third: 'modal' / [omitted]", 'Optional. Include to show Hub promo modals.'], ]; /** @@ -796,13 +797,16 @@ class Debugger { * * Open the Ghostery Intro Hub in a new tab for automation testing. * - * @param {String} [modal=''] Trigger upgrade modal(s) in addition to opening the hub if the value is 'modal'. - * @return {String} A thank you message. + * @param {Boolean} isDawnHub True to open the Dawn Hub. False to open the classic Hub. + * @param {Boolean} justInstalled True to simulate first, on-install visit. False to simulate return visit. + * @param {String} [modal=''] Trigger upgrade modal(s) in addition to opening the hub if the value is 'modal'. + * @return {String} A thank you message. */ - openIntroHub = (modal = '') => { + openIntroHub = (isDawnHub, justInstalled, modal = '') => { + const template = isDawnHub ? 'dawn_hub' : 'hub'; const showModal = modal.toLowerCase() === 'modal'; chrome.tabs.create({ - url: chrome.runtime.getURL(`./app/templates/hub.html?justInstalled=true&pm=${showModal}`), + url: chrome.runtime.getURL(`./app/templates/${template}.html?justInstalled=${justInstalled}&pm=${showModal}`), active: true }); return THANKS; diff --git a/src/classes/Globals.js b/src/classes/Globals.js index 6ba7ad2a4..200f5c979 100644 --- a/src/classes/Globals.js +++ b/src/classes/Globals.js @@ -54,6 +54,8 @@ class Globals { this.GHOSTERY_BASE_URL = `https://${this.GHOSTERY_ROOT_DOMAIN}`; this.ACCOUNT_BASE_URL = `https://account.${this.GHOSTERY_ROOT_DOMAIN}`; this.CHECKOUT_BASE_URL = `https://checkout.${this.GHOSTERY_ROOT_DOMAIN}`; + this.GLOWSTERY_ROOT_DOMAIN = `${this.DEBUG ? 'staging.glowstery' : 'glowstery'}.com`; + this.GLOWSTERY_BASE_URL = `https://${this.GLOWSTERY_ROOT_DOMAIN}`; this.METRICS_BASE_URL = `https://${this.DEBUG ? 'staging-d' : 'd'}.ghostery.com`; this.CMP_BASE_URL = `https://${this.DEBUG ? 'staging-cmp-cdn' : 'cmp-cdn'}.ghostery.com`; this.CDN_BASE_URL = `https://${this.DEBUG ? 'staging-cdn' : 'cdn'}.ghostery.com`; diff --git a/src/classes/Metrics.js b/src/classes/Metrics.js index 0a4d7c16d..2f2598f55 100644 --- a/src/classes/Metrics.js +++ b/src/classes/Metrics.js @@ -184,6 +184,12 @@ class Metrics { this._sendReq(type, ['all']); break; + // Ghostery Browser Hub - Ghostery 8.5.5+ + case 'gb_onboarding': + case 'gb_onboarding_success': + this._sendReq(type, ['all']); + break; + // Uncaught Pings default: log(`metrics ping() error: ping name ${type} not found`); @@ -285,9 +291,9 @@ class Metrics { // Antitracking state this._buildQueryPair('at', conf.enable_anti_tracking ? '1' : '0') + // The deepest setup page reached by user during setup - this._buildQueryPair('ss', (conf.metrics.install_complete_all || type === 'install_complete') ? conf.setup_step.toString() : '-1') + - // The number of times the user has gone through setup - this._buildQueryPair('sl', conf.setup_number.toString()) + + this._buildQueryPair('ss', Metrics._getSetupStep(type).toString()) + + // The number of times the user has gone through setup in the regular hub, or the answer selection on each page of the dawn-hub + this._buildQueryPair('sl', Metrics._getSetupNumber(type).toString()) + // Type of blocking selected during setup this._buildQueryPair('sb', conf.setup_block.toString()) + // Recency, days since last active daily ping @@ -393,6 +399,33 @@ class Metrics { return 'gbe'; } + /** + * Get the Setup step + * + * @private + * + * @return {number} The deepest setup page reached by user during setup + */ + static _getSetupStep(type) { + if (conf.metrics.install_complete_all + || type === 'install_complete' + || type === 'gb_onboarding' + || type === 'gb_onboarding_success') return conf.setup_step; + return -1; + } + + /** + * Get the Setup Number + * + * @private + * + * @return {number} The number of times the user has gone through setup in the regular hub, or the answer selection on each page of the dawn-hub + */ + static _getSetupNumber(type) { + const { setup_number, dawn_setup_number } = conf; + return (type === 'gb_onboarding') ? dawn_setup_number : setup_number; + } + /** * Calculate days since the last daily active ping. * diff --git a/webpack.config.js b/webpack.config.js index 6dc791712..c2f80f823 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,6 +25,7 @@ const SHARED_COMP_DIR = path.resolve(__dirname, 'app/shared-components'); const PANEL_DIR = path.resolve(__dirname, 'app/panel'); const PANEL_ANDROID_DIR = path.resolve(__dirname, 'app/panel-android'); const HUB_DIR = path.resolve(__dirname, 'app/hub'); +const DAWN_HUB_DIR = path.resolve(__dirname, 'app/dawn-hub'); const LICENSES_DIR = path.resolve(__dirname, 'app/licenses'); const SASS_DIR = path.resolve(__dirname, 'app/scss'); const CONTENT_SCRIPTS_DIR = path.resolve(__dirname, 'app/content-scripts'); @@ -50,6 +51,7 @@ module.exports = { click_to_play: [`${CONTENT_SCRIPTS_DIR}/click_to_play.js`], content_script_bundle: [`${CONTENT_SCRIPTS_DIR}/content_script_bundle.js`], hub_react: [`${HUB_DIR}/index.jsx`], + dawn_hub_react: [`${DAWN_HUB_DIR}/index.jsx`], licenses_react: [`${LICENSES_DIR}/Licenses.jsx`, `${LICENSES_DIR}/License.jsx`], notifications: [`${CONTENT_SCRIPTS_DIR}/notifications.js`], page_performance: [`${CONTENT_SCRIPTS_DIR}/page_performance.js`], @@ -61,6 +63,7 @@ module.exports = { foundation: [`${SASS_DIR}/vendor/foundation.scss`], foundation_hub: [`${SASS_DIR}/vendor/foundation_hub.scss`], hub: [`${SASS_DIR}/hub.scss`], + dawn_hub: [`${SASS_DIR}/dawn_hub.scss`], licenses: [`${SASS_DIR}/licenses.scss`], panel: [`${SASS_DIR}/panel.scss`], panel_android: [`${SASS_DIR}/panel_android.scss`], @@ -87,6 +90,7 @@ module.exports = { `${RM} ./dist/foundation.js`, `${RM} ./dist/foundation_hub.js`, `${RM} ./dist/hub.js`, + `${RM} ./dist/dawn_hub.js`, `${RM} ./dist/licenses.js`, `${RM} ./dist/panel.js`, `${RM} ./dist/panel_android.js`, @@ -115,7 +119,7 @@ module.exports = { } }, { test: /\.(js|jsx)$/, - include: [SHARED_COMP_DIR, PANEL_ANDROID_DIR, PANEL_DIR, HUB_DIR, LICENSES_DIR, CONTENT_SCRIPTS_DIR], + include: [SHARED_COMP_DIR, PANEL_ANDROID_DIR, PANEL_DIR, HUB_DIR, DAWN_HUB_DIR, LICENSES_DIR, CONTENT_SCRIPTS_DIR], exclude: /node_modules/, use: [ { diff --git a/yarn.lock b/yarn.lock index e68aee61c..02e1aeafe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -618,37 +618,47 @@ resolved "https://registry.yarnpkg.com/@cliqz-oss/dexie/-/dexie-2.0.4.tgz#0e710504e2b9198baa9b046abd3a82731b94d56e" integrity sha512-HxMbBQfdy0CehThTFierXbRPI+PHDEucUUriCCzViAKbCWWQIlL6uZcyDaaPRMPWy45v78lezPB4457kfjS72g== -"@cliqz/adblocker-content@^1.18.8": - version "1.18.8" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.18.8.tgz#96473f14c098a20091298d34a6addcd430aceebd" - integrity sha512-YZ1xYBVG3LmxsdTYvTs/Bc7pzCw/Dy4HFo6N+oIuGP+Le/0aGSkACUl3ue5I2+Cx0WmL0Z8I4QonTKDc06HR+A== +"@cliqz/adblocker-content@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-content/-/adblocker-content-1.20.0.tgz#fcfa2845a577ba8d9af282afbae2fc437b3f1c70" + integrity sha512-KcokmK2B+tAnVMi7nGHgzXUVf78wAODG1Uk+K3tBPf9VAo3mwldYZ472uTj6LUfZv5oeTwe4PwfmPWXWZy3Eew== + dependencies: + "@cliqz/adblocker-extended-selectors" "^1.20.0" + +"@cliqz/adblocker-extended-selectors@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-extended-selectors/-/adblocker-extended-selectors-1.20.0.tgz#95ede657b670f627b39f92d85a97093cecee6ffe" + integrity sha512-dnBPIngGe1eDWvYX49eP2yyCE2AY1QD5E+8SaXW6lslnjS0GQnkcXCAkkGR2am4Qdk78HAiWTXL65Zt9hdkupA== -"@cliqz/adblocker-webextension-cosmetics@^1.18.8": - version "1.18.8" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-webextension-cosmetics/-/adblocker-webextension-cosmetics-1.18.8.tgz#a1064393bb19cef2c2d80f0aa2cbbe546eec564a" - integrity sha512-epQLl7POmuH5l1WYXEzR6TYZktgxxJ+UVXEAkN1CX98PJfdEGtbzL7C2gaBdstLSuAIdCZQ9Xrw4BLF2MoFlmw== +"@cliqz/adblocker-webextension-cosmetics@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-webextension-cosmetics/-/adblocker-webextension-cosmetics-1.20.0.tgz#ea35811fe25bd19f16cd9ddccdd818c292025ef0" + integrity sha512-AkfuXHzMw/L9rq9iLzw5bzjU7EgBDpWL6eR/VB9gqrPeprj8NR8jDH74DFIzW6KZ4CGM77q1WEz3LUEG3d3nfA== dependencies: - "@cliqz/adblocker-content" "^1.18.8" + "@cliqz/adblocker-content" "^1.20.0" + "@cliqz/adblocker-extended-selectors" "^1.20.0" -"@cliqz/adblocker-webextension@^1.18.8": - version "1.18.8" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker-webextension/-/adblocker-webextension-1.18.8.tgz#07a3fa5bdb2171bd1f13b84889c33b67232bc192" - integrity sha512-ZdgIEmETSOmg5TEs54KqwsALCB2kQpYN+Tqhelr0T5G2N+YHvZHc8t5V+uit829jEGHKPhHjSBhDkfDcjHK3Jw== +"@cliqz/adblocker-webextension@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker-webextension/-/adblocker-webextension-1.20.0.tgz#f914c43067b501797828e84f31b8d05a0cbd2e3a" + integrity sha512-AuIa04SzCZUTphVtZx75tsbejO9k5kiI71rzzTdELt7peW2aNatyXTtzIe2f62HqMKgiMKCTZZiPi6Fak7Jvkg== dependencies: - "@cliqz/adblocker" "^1.18.8" - "@cliqz/adblocker-content" "^1.18.8" + "@cliqz/adblocker" "^1.20.0" + "@cliqz/adblocker-content" "^1.20.0" tldts-experimental "^5.6.21" webextension-polyfill-ts "^0.22.0" -"@cliqz/adblocker@^1.18.8": - version "1.18.8" - resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.18.8.tgz#f6e5724fe6573c2e68f2545d90bcce3e1ecfbae9" - integrity sha512-19m0GhlOcdSvQ/BqVuaMgbYkgQ4ys8koBRW4K7Ua4V5fFWL0t8ckdcZ/gBOqwECS2m8agXSpEbbyJjNmHBHpMQ== +"@cliqz/adblocker@^1.20.0": + version "1.20.0" + resolved "https://registry.yarnpkg.com/@cliqz/adblocker/-/adblocker-1.20.0.tgz#514746e9ee72fcd886f1e2e1aaf13b28fc63f232" + integrity sha512-lkEj0Pj1ikwMURrvoFv0YnLfaXFuJI+jexI7zdh4fDmlwRppzDDgOhPXgCczoAlYacJk5x2mf7pan6JybRD9Kw== dependencies: + "@cliqz/adblocker-content" "^1.20.0" + "@cliqz/adblocker-extended-selectors" "^1.20.0" "@remusao/guess-url-type" "^1.1.2" "@remusao/small" "^1.1.2" "@remusao/smaz" "^1.7.1" - "@types/chrome" "^0.0.126" + "@types/chrome" "^0.0.128" "@types/firefox-webext-browser" "^82.0.0" tldts-experimental "^5.6.21" @@ -1013,10 +1023,10 @@ dependencies: "@babel/types" "^7.3.0" -"@types/chrome@^0.0.126": - version "0.0.126" - resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.126.tgz#f9f3436712f0c7c12ea9798abc9b95575ad7b23a" - integrity sha512-191z7uoyfbGU+z7/m45j9XbWugWqVHVPMM4hJV5cZ+3YzGCT9wFjMUHO3Wr3Xvo8aVodvRNu28u7lvEaAnfbzg== +"@types/chrome@^0.0.128": + version "0.0.128" + resolved "https://registry.yarnpkg.com/@types/chrome/-/chrome-0.0.128.tgz#5dbd8b2539a367353fbe4386f119b510105f8b6a" + integrity sha512-eGc599TDtersMBW1cSnExHm0IHrXrO5xdk6Sa2Dq30ED+hR1rpT1ez0NNcCgvGO52nmktGfyvd3Uyquzv3LL4g== dependencies: "@types/filesystem" "*" "@types/har-format" "*" @@ -4043,14 +4053,14 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -ghostery-common@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/ghostery-common/-/ghostery-common-1.2.0.tgz#84d7bbe7c29fbf488e4668853ffeef76d394fee1" - integrity sha512-J/cSrlgXjrCUTqToroMihWwsKuuKGXhWvLg+n9p5sFr1HHUvOoe7hNRaXfWLzcCfjPIawIvnEf3eJYGlKrleTg== +ghostery-common@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ghostery-common/-/ghostery-common-1.2.1.tgz#ad201b6e959633c3f5bdcababe6ea9ce03e2ae2c" + integrity sha512-BidnDzj/emiR+BUaQ6FwAYYZJda0aqPid/mhbEx5IJGogwHh5lGnmxwADH1DAA442GbW+zgZx2e2cFjC/dJGwA== dependencies: "@cliqz-oss/dexie" "^2.0.4" - "@cliqz/adblocker-webextension" "^1.18.8" - "@cliqz/adblocker-webextension-cosmetics" "^1.18.8" + "@cliqz/adblocker-webextension" "^1.20.0" + "@cliqz/adblocker-webextension-cosmetics" "^1.20.0" "@cliqz/url-parser" "^1.1.4" abortcontroller-polyfill "^1.5.0" anonymous-credentials "https://github.com/cliqz-oss/anonymous-credentials/releases/download/1.0.0/anonymous-credentials-1.0.0.tgz"