diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 42c00d1fb..b1f614eb9 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -1722,6 +1722,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?"
},
@@ -1758,6 +1761,66 @@
"ghostery_browser_hub_onboarding_lets_do_this": {
"message": "Let's do this"
},
+ "ghostery_browser_hub_onboarding_create_a_ghostery_account": {
+ "message": "Create a Ghostery Account"
+ },
+ "ghostery_browser_hub_onboarding_sync_settings": {
+ "message": "Allows you to sync settings across browsers and devices"
+ },
+ "ghostery_browser_hub_onboarding_already_have_account": {
+ "message": "I already have an account."
+ },
+ "ghostery_browser_hub_onboarding_send_me": {
+ "message": "Send me"
+ },
+ "ghostery_browser_hub_onboarding_updates_and_promotions": {
+ "message": "updates & promotions."
+ },
+ "ghostery_browser_hub_onboarding_skip": {
+ "message": "Skip"
+ },
+ "ghostery_browser_hub_onboarding_we_take_your_privacy_very_seriously": {
+ "message": "We take your privacy very seriously. Learn more"
+ },
+ "ghostery_browser_hub_onboarding_private_by_design": {
+ "message": "Private by design"
+ },
+ "ghostery_browser_hub_onboarding_private_by_design_description": {
+ "message": "Privacy is incredibly important to us at Ghostery and we've taken extra care to design our products that limits the amount of personal information collected."
+ },
+ "ghostery_browser_hub_onboarding_why_do_you_need_my_email": {
+ "message": "Why do you need my email?"
+ },
+ "ghostery_browser_hub_onboarding_why_do_you_need_my_email_description": {
+ "message": "We've found email is the simplest way to create and secure a unique account without sharing your information. It helps sync your custom settings across your devices."
+ },
+ "ghostery_browser_hub_onboarding_what_do_you_use_my_email_for": {
+ "message": "What do you use my email for?"
+ },
+ "ghostery_browser_hub_onboarding_what_do_you_use_my_email_for_description": {
+ "message": "We never share or sell your personal information. Your email address is used for account management, technical support, product-related communications, and some opt-in features."
+ },
+ "ghostery_browser_hub_onboarding_how_secure_is_": {
+ "message": "How secure is"
+ },
+ "ghostery_browser_hub_onboarding_how_secure_is_ghostery_description": {
+ "message": "We never share or sell your personal information. Your email address is used for account management, technical support, product-related communications, and some opt-in features."
+ },
+ "ghostery_browser_hub_onboarding_can_i_remove_my_account": {
+ "message": "Can I remove my account?"
+ },
+ "ghostery_browser_hub_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_browser_hub_onboarding_visit_our_privacy_policy": {
+ "message": "Visit our Privacy Policy for more information"
+ },
+ "ghostery_browser_hub_onboarding_create_an_account": {
+ "message": "Create an account."
+ },
+ "ghostery_browser_hub_onboarding_you_are_signed_in_as": {
+ "message": "You are signed in as"
+ },
"ghostery_browser_hub_onboarding_yay_youre_all_set": {
"message": "Yay! You're all set."
},
@@ -2087,6 +2150,9 @@
"password_colon": {
"message": "Password:"
},
+ "confirm_password_colon": {
+ "message": "Confirm Password:"
+ },
"please_enter_a_valid_email": {
"message": "Please enter a valid email."
},
diff --git a/app/Account/AccountActions.js b/app/Account/AccountActions.js
index 4114a025b..c51317dec 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,
+ ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE
} 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 handleEmailPreferencesCheckboxChange = (name, checked) => dispatch => (
+ dispatch({
+ type: ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE,
+ payload: { name, checked },
+ })
+);
diff --git a/app/Account/AccountConstants.js b/app/Account/AccountConstants.js
index 64eb24a63..0f95a09c4 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 ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE = 'ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE';
diff --git a/app/Account/AccountReducer.js b/app/Account/AccountReducer.js
index ebc31da25..0a91127a1 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,
+ ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE
} from './AccountConstants';
import { UPDATE_PANEL_DATA } from '../panel/constants/constants';
@@ -118,6 +119,15 @@ export default (state = initialState, action) => {
resetPasswordError: true
};
}
+ case ACCOUNT_DATA_EMAIL_PREFERENCES_CHECKBOX_CHANGE: {
+ const { name, checked } = action.payload;
+ let emailPreferences;
+ if (name === 'global') {
+ emailPreferences = { ...state.user.emailPreferences, ...{ global: checked } };
+ }
+ const user = { ...state.user, ...{ emailPreferences } };
+ return { ...state, ...{ user } };
+ }
default: return state;
}
diff --git a/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx
new file mode 100644
index 000000000..2b2782fd4
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.jsx
@@ -0,0 +1,256 @@
+/**
+ * Browser Create Account Form
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React, { useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+import { NavLink } from 'react-router-dom';
+import { ToggleCheckbox } from '../../../shared-components';
+
+const promoString = `${t('ghostery_browser_hub_onboarding_send_me')} Ghostery ${t('ghostery_browser_hub_onboarding_updates_and_promotions')}`;
+
+/**
+ * A Functional React component for rendering the Browser Create Account View
+ * @return {JSX} JSX for rendering the Browser Create Account View of the Hub app
+ * @memberof HubComponents
+ */
+const Step1_CreateAccountForm = (props) => {
+ const {
+ email,
+ emailError,
+ confirmEmail,
+ confirmEmailError,
+ firstName,
+ lastName,
+ password,
+ passwordInvalidError,
+ passwordLengthError,
+ confirmPassword,
+ confirmPasswordError,
+ legalConsentChecked,
+ legalConsentNotCheckedError,
+ handleLegalConsentCheckboxChange,
+ isUpdatesChecked,
+ handleInputChange,
+ handleUpdatesCheckboxChange,
+ 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 (
+
+ );
+};
+
+// 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,
+ isUpdatesChecked: PropTypes.bool.isRequired,
+ password: PropTypes.string.isRequired,
+ confirmPassword: PropTypes.string.isRequired,
+ passwordInvalidError: PropTypes.bool.isRequired,
+ passwordLengthError: PropTypes.bool.isRequired,
+ handleInputChange: PropTypes.func.isRequired,
+ handleUpdatesCheckboxChange: PropTypes.func.isRequired,
+ handleSubmit: PropTypes.func.isRequired,
+};
+
+export default Step1_CreateAccountForm;
diff --git a/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss
new file mode 100644
index 000000000..964008ede
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss
@@ -0,0 +1,107 @@
+/**
+ * Step1_CreateAccountForm Sass
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+.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__promoString {
+ font-size: 14px;
+ margin-top: 15px;
+ @include breakpoint(small down) {
+ margin-top: 0;
+ }
+}
+.Step1_CreateAccountForm__legalConsentCheckedLabel {
+ font-size: 14px;
+ @include breakpoint(small down) {
+ width: 258px;
+ }
+
+ &.error {
+ color: red;
+ }
+}
+.Step1_CreateAccountForm__ctaButtonContainer {
+ display: flex;
+ justify-content: center;
+
+ .Step1_CreateAccountForm__ctaButton {
+ margin: 48px auto 0 auto;
+ 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/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx
new file mode 100644
index 000000000..8d043ea9a
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountFormContainer.jsx
@@ -0,0 +1,240 @@
+/**
+ * Create Account Form Container
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React, { 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 Ghostery Hub
+ * @extends Component
+ * @memberof HubContainers
+ */
+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 }));
+ }
+
+ /**
+ * Update updates checkbox value by updating state
+ */
+ _handleUpdatesCheckboxChange = () => {
+ this.setState(prevState => ({ isUpdatesChecked: !prevState.isUpdatesChecked }));
+ }
+
+ /**
+ * 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,
+ isUpdatesChecked,
+ } = 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,
+ validateInput: true,
+ });
+
+ if (!emailIsValid || !confirmIsValid || !legalConsentChecked || !passwordIsValid || confirmPasswordError) {
+ return;
+ }
+ const { actions } = this.props;
+ actions.setToast({
+ toastMessage: '',
+ toastClass: ''
+ });
+ actions.register(email, confirmEmail, firstName, lastName, password).then((success) => {
+ if (success) {
+ actions.getUser().then(() => {
+ if (isUpdatesChecked) actions.handleEmailPreferencesCheckboxChange('global', isUpdatesChecked);
+ });
+ // Toggle legal consent checked here
+ actions.setToast({
+ toastMessage: t('hub_create_account_toast_success'),
+ toastClass: 'success'
+ });
+ // Route to next screen
+ } 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/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js
new file mode 100644
index 000000000..30c614789
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/index.js
@@ -0,0 +1,44 @@
+/**
+ * Point of entry index.js file for Ghostery Browser Hub Create Account Form
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2020 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import Step1_CreateAccountFormContainer from './Step1_CreateAccountFormContainer';
+import { register, getUser, handleEmailPreferencesCheckboxChange } from '../../../../Account/AccountActions';
+import { setToast } from '../../../../hub/Views/AppView/AppViewActions';
+
+/**
+ * Map redux store state properties to the component's own properties.
+ * @param {Object} state entire Redux store's state
+ * @return {function} this function returns a plain object, which will be merged into the component's props
+ * @memberof HubContainers
+ */
+const mapStateToProps = state => ({ ...state.account });
+
+/**
+ * Bind the component's action creators using Redux's bindActionCreators.
+ * @param {function} dispatch redux store method which dispatches actions
+ * @return {function} to be used as an argument in redux connect call
+ * @memberof SetupContainers
+ */
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators({
+ setToast,
+ register,
+ getUser,
+ handleEmailPreferencesCheckboxChange
+ }, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Step1_CreateAccountFormContainer);
diff --git a/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx
new file mode 100644
index 000000000..d97910485
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.jsx
@@ -0,0 +1,175 @@
+/**
+ * Browser Create Account View
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React, { Fragment, useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+import { NavLink } from 'react-router-dom';
+import Step1_LogInForm from '../Step1_LogInForm';
+import Step1_CreateAccountForm from '../Step1_CreateAccountForm';
+import globals from '../../../../../src/classes/Globals';
+
+const SIGN_IN = 'SIGN_IN';
+const CREATE_ACCOUNT = 'CREATE_ACCOUNT';
+
+const faqList = [
+ {
+ icon: 'ghosty-shield.svg',
+ label: t('ghostery_browser_hub_onboarding_private_by_design'),
+ description: t('ghostery_browser_hub_onboarding_private_by_design_description'),
+ },
+ {
+ icon: 'ghosty-letter.svg',
+ label: t('ghostery_browser_hub_onboarding_why_do_you_need_my_email'),
+ description: t('ghostery_browser_hub_onboarding_why_do_you_need_my_email_description'),
+ },
+ {
+ icon: 'ghosty-shield-letter.svg',
+ label: t('ghostery_browser_hub_onboarding_what_do_you_use_my_email_for'),
+ description: t('ghostery_browser_hub_onboarding_what_do_you_use_my_email_for_description'),
+ },
+ {
+ icon: 'ghosty-lock.svg',
+ label: `${t('ghostery_browser_hub_onboarding_how_secure_is_')} Ghostery?`,
+ description: t('ghostery_browser_hub_onboarding_how_secure_is_ghostery_description'),
+ },
+ {
+ icon: 'ghosty-box.svg',
+ label: t('ghostery_browser_hub_onboarding_can_i_remove_my_account'),
+ description: t('ghostery_browser_hub_onboarding_can_i_remove_my_account_description'),
+ }
+];
+
+const renderFAQListItem = (icon, label, description) => (
+
+
+

+
+
+
{label}
+
{description}
+
+
+);
+
+const renderSkipLink = () => (
+
+
+
+
{t('ghostery_browser_hub_onboarding_skip')}
+
+
+);
+
+/**
+ * A Functional React component for rendering the Browser Create Account View
+ * @return {JSX} JSX for rendering the Browser Create Account View of the Hub app
+ * @memberof HubComponents
+ */
+const Step1_CreateAccountView = (props) => {
+ const { user } = props;
+ const email = user && user.email;
+
+ const [expanded, setExpanded] = useState(false);
+ const [view, setView] = useState(CREATE_ACCOUNT);
+
+ const arrowClassNames = ClassNames('Step1_CreateAccountView__arrow', {
+ up: expanded,
+ down: !expanded,
+ });
+
+ const faqRef = useRef(null);
+ const scrollToFAQ = () => {
+ faqRef.current.scrollIntoView({ behavior: 'smooth' });
+ };
+
+ const handleFAQLearnMoreClick = () => {
+ setTimeout(scrollToFAQ, 1);
+ setExpanded(!expanded);
+ };
+
+ return (user ? (
+
+
{t('ghostery_browser_hub_onboarding_you_are_signed_in_as')}
+
{email}
+
+ {/* Link to next page */}
+
+
+
+ ) : (
+
+ {view === CREATE_ACCOUNT && (
+
{t('ghostery_browser_hub_onboarding_create_a_ghostery_account')}
+ )}
+ {view === SIGN_IN && (
+
{t('sign_in')}
+ )}
+
{ t('ghostery_browser_hub_onboarding_sync_settings') }
+
+ {view === CREATE_ACCOUNT && (
+
setView(SIGN_IN)}>{t('ghostery_browser_hub_onboarding_already_have_account')}
+ )}
+ {view === SIGN_IN && (
+
setView(CREATE_ACCOUNT)}>{t('ghostery_browser_hub_onboarding_create_an_account')}
+ )}
+
+
+ {view === CREATE_ACCOUNT ? (
+
+ {/* eslint-disable-next-line react/jsx-pascal-case */}
+
+ {renderSkipLink()}
+
+
{t('ghostery_browser_hub_onboarding_we_take_your_privacy_very_seriously')}
+
+
+
+ {expanded &&
+ faqList.map(item => renderFAQListItem(item.icon, item.label, item.description))
+ }
+
+ {expanded && (
+
+ )}
+
+ ) : (
+
+ {/* eslint-disable-next-line react/jsx-pascal-case */}
+
+ {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/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss
new file mode 100644
index 000000000..d79bf60a2
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss
@@ -0,0 +1,269 @@
+/**
+ * Step1_CreateAccountView Sass
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+$color-create-account-form-error-red: #e74055;
+
+// Browser Create Account View
+.Step1_CreateAccountView {
+ padding-top: 80px;
+ padding-bottom: 40px;
+}
+.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;
+ 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-top: 10px;
+ display: flex;
+ justify-content: center;
+ font-size: 18px;
+ text-align: center;
+ @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;
+ text-align: 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__arrow {
+ margin: 15px auto 0 auto;
+ height: 12px;
+ width: 12px;
+ border-left: 2px solid #00aef0;
+ border-top: 2px solid #00aef0;
+ cursor: pointer;
+
+ &.up {
+ transform: rotate(45deg);
+ }
+ &.down {
+ transform: rotate(225deg);
+ }
+}
+.Step1_CreateAccountView__FAQContainer {
+ margin-top: 10px;
+ padding-top: 20px;
+ @include breakpoint(small down) {
+ text-align: center;
+ }
+}
+.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: 20px;
+ }
+ .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/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js
new file mode 100644
index 000000000..d8bc43c75
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/index.js
@@ -0,0 +1,35 @@
+/**
+ * Point of entry index.js file for Ghostery Browser Hub Create Account View
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2020 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { connect } from 'react-redux';
+import { withRouter } from 'react-router-dom';
+
+import Step1_CreateAccountView from './Step1_CreateAccountView';
+
+/**
+ * Map redux store state properties to the component's own properties.
+ * @param {Object} state entire Redux store's state
+ * @return {function} this function returns a plain object, which will be merged into the component's props
+ * @memberof HubContainers
+ */
+const mapStateToProps = state => ({ ...state.account });
+
+/**
+ * Bind the component's action creators using Redux's bindActionCreators.
+ * @param {function} dispatch redux store method which dispatches actions
+ * @return {function} to be used as an argument in redux connect call
+ * @memberof SetupContainers
+ */
+const mapDispatchToProps = dispatch => ({});
+
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Step1_CreateAccountView));
diff --git a/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx
new file mode 100644
index 000000000..bc712d4cc
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.jsx
@@ -0,0 +1,114 @@
+/**
+ * Browser Log In Form
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import ClassNames from 'classnames';
+import { NavLink } from 'react-router-dom';
+
+/**
+ * A Functional React component for rendering the Log In Form
+ * @return {JSX} JSX for rendering the Log In Form of the Browser Hub app
+ * @memberof HubComponents
+ */
+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 (
+
+ );
+};
+
+// 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/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss
new file mode 100644
index 000000000..9121a1d53
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss
@@ -0,0 +1,109 @@
+/**
+ * Step1_LogInForm Sass
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+.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;
+ 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/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx
new file mode 100644
index 000000000..e957e3dfa
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInFormContainer.jsx
@@ -0,0 +1,187 @@
+/**
+ * Step1_LogInFormContainer
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2019 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import React, { 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 Ghostery Hub
+ * @extends Component
+ * @memberof HubContainers
+ */
+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();
+ 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/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/index.js b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/index.js
new file mode 100644
index 000000000..17b0ba241
--- /dev/null
+++ b/app/ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/index.js
@@ -0,0 +1,52 @@
+/**
+ * Point of entry index.js file for Ghostery Browser Hub Onboarding Login View
+ *
+ * Ghostery Browser Extension
+ * https://www.ghostery.com/
+ *
+ * Copyright 2020 Ghostery, Inc. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0
+ */
+
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+
+import Step1_LogInFormContainer from './Step1_LogInFormContainer';
+import {
+ login,
+ getUser,
+ getUserSettings,
+ resetPassword
+} from '../../../Account/AccountActions';
+import { getTheme } from '../../../panel/actions/PanelActions';
+import { setToast } from '../AppView/AppViewActions';
+
+/**
+ * Map redux store state properties to the component's own properties.
+ * @param {Object} state entire Redux store's state
+ * @return {function} this function returns a plain object, which will be merged into the component's props
+ * @memberof HubContainers
+ */
+const mapStateToProps = state => ({ ...state.account });
+
+/**
+ * Bind the component's action creators using Redux's bindActionCreators.
+ * @param {function} dispatch redux store method which dispatches actions
+ * @return {function} to be used as an argument in redux connect call
+ * @memberof SetupContainers
+ */
+const mapDispatchToProps = dispatch => ({
+ actions: bindActionCreators({
+ setToast,
+ login,
+ getUser,
+ getUserSettings,
+ getTheme,
+ resetPassword
+ }, dispatch),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Step1_LogInFormContainer);
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-letter.svg b/app/images/hub/browser-create-account-view/ghosty-letter.svg
new file mode 100644
index 000000000..2921eee09
--- /dev/null
+++ b/app/images/hub/browser-create-account-view/ghosty-letter.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/images/hub/browser-create-account-view/ghosty-lock.svg b/app/images/hub/browser-create-account-view/ghosty-lock.svg
new file mode 100644
index 000000000..03f9465db
--- /dev/null
+++ b/app/images/hub/browser-create-account-view/ghosty-lock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/app/images/hub/browser-create-account-view/ghosty-shield-letter.svg b/app/images/hub/browser-create-account-view/ghosty-shield-letter.svg
new file mode 100644
index 000000000..2fb520cf9
--- /dev/null
+++ b/app/images/hub/browser-create-account-view/ghosty-shield-letter.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/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/hub.scss b/app/scss/hub.scss
index b5e5780fb..07e777997 100644
--- a/app/scss/hub.scss
+++ b/app/scss/hub.scss
@@ -87,6 +87,9 @@ html, body, #root {
@import '../hub/Views/CreateAccountView/CreateAccountView.scss';
@import '../hub/Views/UpgradePlanView/UpgradePlanView.scss';
@import '../ghostery-browser-hub/Views/OnboardingViews/Step0_WelcomeView/WelcomeView.scss';
+@import '../ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountView/Step1_CreateAccountView.scss';
+@import '../ghostery-browser-hub/Views/OnboardingViews/Step1_CreateAccountForm/Step1_CreateAccountForm.scss';
+@import '../ghostery-browser-hub/Views/OnboardingViews/Step1_LogInForm/Step1_LogInForm.scss';
@import '../ghostery-browser-hub/Views/OnboardingViews/Step5_SuccessView/SuccessView.scss';
// Imports from ../shared-components directory