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 ( +
+
+
+ + + {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, + 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 ( +
+
+
+ + + {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/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