diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8a02576ff..3eee43116 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -500,7 +500,7 @@ } } }, - "panel_forgot_password": { + "forgot_password": { "message": "Forgot Password?" }, "panel_help_panel_header": { diff --git a/app/Account/AccountReducer.js b/app/Account/AccountReducer.js index 148070774..48076f6b0 100644 --- a/app/Account/AccountReducer.js +++ b/app/Account/AccountReducer.js @@ -18,7 +18,9 @@ import { GET_USER_SUCCESS, GET_USER_SETTINGS_SUCCESS, GET_USER_SUBSCRIPTION_DATA_FAIL, - GET_USER_SUBSCRIPTION_DATA_SUCCESS + GET_USER_SUBSCRIPTION_DATA_SUCCESS, + RESET_PASSWORD_SUCCESS, + RESET_PASSWORD_FAIL } from './AccountConstants'; import { UPDATE_PANEL_DATA } from '../panel/constants/constants'; @@ -28,6 +30,8 @@ const initialState = { user: null, userSettings: null, subscriptionData: null, + toastMessage: '', + resetPasswordError: false }; export default (state = initialState, action) => { @@ -84,6 +88,31 @@ export default (state = initialState, action) => { subscriptionData }); } + case RESET_PASSWORD_SUCCESS: { + const toastMessage = t('banner_check_your_email_title'); + return Object.assign({}, state, { + toastMessage, + resetPasswordError: false + }); + } + case RESET_PASSWORD_FAIL: { + const { errors } = action.payload; + let errorText = t('server_error_message'); + errors.forEach((err) => { + switch (err.code) { + case '10050': + case '10110': + errorText = t('banner_email_not_in_system_message'); + break; + default: + errorText = t('server_error_message'); + } + }); + return Object.assign({}, state, { + toastMessage: errorText, + resetPasswordError: true + }); + } default: return state; } diff --git a/app/hub/Views/LogInView/LogInView.jsx b/app/hub/Views/LogInView/LogInView.jsx index 6e76b3462..6e1cba87d 100644 --- a/app/hub/Views/LogInView/LogInView.jsx +++ b/app/hub/Views/LogInView/LogInView.jsx @@ -90,6 +90,13 @@ const LogInView = (props) => { {t('hub_login_label_password_invalid')} )} +
+ + + { t('forgot_password') } + + +
{ t('hub_login_link_dont_have_account') } diff --git a/app/hub/Views/LogInView/__tests__/__snapshots__/LogInView.test.jsx.snap b/app/hub/Views/LogInView/__tests__/__snapshots__/LogInView.test.jsx.snap index d6b6f0e0f..0c28a94c9 100644 --- a/app/hub/Views/LogInView/__tests__/__snapshots__/LogInView.test.jsx.snap +++ b/app/hub/Views/LogInView/__tests__/__snapshots__/LogInView.test.jsx.snap @@ -67,6 +67,21 @@ exports[`app/hub/Views/LogIn component Snapshot tests with react-test-renderer l type="password" value="examplePassword" /> +
+ + + forgot_password + + +
@@ -177,6 +192,21 @@ exports[`app/hub/Views/LogIn component Snapshot tests with react-test-renderer l > hub_login_label_password_invalid
+
+ + + forgot_password + + +
diff --git a/app/hub/index.jsx b/app/hub/index.jsx index 202a0db33..9f6590bbe 100644 --- a/app/hub/index.jsx +++ b/app/hub/index.jsx @@ -28,6 +28,7 @@ import PlusView from './Views/PlusView'; import RewardsView from './Views/RewardsView'; import ProductsView from './Views/ProductsView'; import CreateAccountView from './Views/CreateAccountView'; +import ForgotPasswordView from '../shared-components/ForgotPassword/ForgotPasswordContainer'; import LogInView from './Views/LogInView'; const store = createStore(); @@ -45,6 +46,7 @@ const Hub = () => ( + } /> ); diff --git a/app/panel/components/Login.jsx b/app/panel/components/Login.jsx index 6d793515c..a7b7562d4 100644 --- a/app/panel/components/Login.jsx +++ b/app/panel/components/Login.jsx @@ -137,7 +137,7 @@ class Login extends React.Component {
- { t('panel_forgot_password') } + { t('forgot_password') }
{ t('create_account') } diff --git a/app/panel/index.jsx b/app/panel/index.jsx index f9d258453..3ab83e39e 100644 --- a/app/panel/index.jsx +++ b/app/panel/index.jsx @@ -25,7 +25,7 @@ import Settings from './containers/SettingsContainer'; import Subscription from './containers/SubscriptionContainer'; import Login from './containers/LoginContainer'; import CreateAccount from './containers/CreateAccountContainer'; -import ForgotPassword from './containers/ForgotPasswordContainer'; +import ForgotPassword from '../shared-components/ForgotPassword/ForgotPasswordContainer'; import AccountSuccess from './containers/AccountSuccessContainer'; import configureStore from './store/configureStore'; import Help from './components/Help'; @@ -51,7 +51,7 @@ const Ghostery = () => ( - + } /> ); diff --git a/app/scss/hub.scss b/app/scss/hub.scss index 9cc1a696b..c53e21f19 100644 --- a/app/scss/hub.scss +++ b/app/scss/hub.scss @@ -81,3 +81,4 @@ html, body, #root { @import '../shared-components/ToastMessage/ToastMessage.scss'; @import '../shared-components/ToggleCheckbox/ToggleCheckbox.scss'; @import '../shared-components/ToggleSwitch/ToggleSwitch.scss'; +@import '../shared-components/ForgotPassword/ForgotPassword.scss'; diff --git a/app/scss/panel.scss b/app/scss/panel.scss index cb9ea1ca0..f1b45e2c7 100644 --- a/app/scss/panel.scss +++ b/app/scss/panel.scss @@ -81,3 +81,4 @@ html body { // Imports from ../shared-components directory @import '../shared-components/Modal/Modal.scss'; @import '../shared-components/PremiumPromoModal/PremiumPromoModal.scss'; +@import '../shared-components/ForgotPassword/ForgotPassword.scss' diff --git a/app/panel/components/ForgotPassword.jsx b/app/shared-components/ForgotPassword/ForgotPassword.jsx similarity index 66% rename from app/panel/components/ForgotPassword.jsx rename to app/shared-components/ForgotPassword/ForgotPassword.jsx index b1d0f7bbe..1c4fe6621 100644 --- a/app/panel/components/ForgotPassword.jsx +++ b/app/shared-components/ForgotPassword/ForgotPassword.jsx @@ -12,11 +12,11 @@ */ import React from 'react'; -import { Link } from 'react-router-dom'; +import { withRouter, Link } from 'react-router-dom'; import ClassNames from 'classnames'; -import { validateEmail } from '../utils/utils'; +import { validateEmail } from '../../panel/utils/utils'; /** - * @class Implement Forgot Password view which opens from the link on Sign In panel. + * @class Implement shared Forgot Password view which opens from the link on Sign In page inside the panel and hub * @memberof PanelClasses */ class ForgotPassword extends React.Component { @@ -46,6 +46,7 @@ class ForgotPassword extends React.Component { e.preventDefault(); this.setState({ loading: true }, () => { const { email } = this.state; + const { locale } = this.props; // validate the email and password if (!validateEmail(email)) { @@ -59,7 +60,14 @@ class ForgotPassword extends React.Component { this.props.actions.resetPassword(email) .then((success) => { this.setState({ loading: false }); - if (success) { + if (success && locale === 'hub') { + this.props.history.push('/log-in'); + + this.props.actions.setToast({ + toastMessage: t('banner_check_your_email_title'), + toastClass: 'success', + }); + } else if (success && locale === 'panel') { this.props.history.push('/login'); } }); @@ -72,20 +80,39 @@ class ForgotPassword extends React.Component { */ render() { const { email, loading, emailError } = this.state; + const { locale } = this.props; const buttonClasses = ClassNames('button ghostery-button', { loading }); + + const ContainerClassNames = ClassNames('', { + 'forgot-password-panel': locale === 'panel', + ForgotPasswordView: locale === 'hub', + }); + const MessageClassNames = ClassNames('', { + 'forgot-password-message': locale === 'panel', + ForgotPasswordMessage: locale === 'hub', + }); + const EmailClassNames = ClassNames('', { + 'forgot-input-email': locale === 'panel', + ForgotPasswordMessage: locale === 'hub', + }); + + const ButtonsContainerClassNames = ClassNames('row', { + 'buttons-container': locale === 'panel', + ForgotPasswordButtonsContainer: locale === 'hub', + }); return ( -
+
-

+

{ t('forgot_password_message') }

-
-
+
{ t('button_cancel') } @@ -115,4 +142,4 @@ class ForgotPassword extends React.Component { } } -export default ForgotPassword; +export default withRouter(ForgotPassword); diff --git a/app/shared-components/ForgotPassword/ForgotPassword.scss b/app/shared-components/ForgotPassword/ForgotPassword.scss new file mode 100644 index 000000000..78ea1ef31 --- /dev/null +++ b/app/shared-components/ForgotPassword/ForgotPassword.scss @@ -0,0 +1,182 @@ +/** + * Forgot Password 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 + */ + +/* FORGOT PASSWORD PANEL */ +#forgot-password-panel{ + margin-top: 75px; + h4#forgot-password-message { + font-size:14px; + font-weight: normal; + color: #333333; + margin-bottom: 50px; + } + + .buttons-container { + margin-top: 60px; + #send-button { + width:126px; + } + } +} + +/* FORGOT PASSWORD HUB */ +#ForgotPasswordView { + margin: 40px auto; + padding-bottom: 40px; + #forgot-email { + margin-top: 40px; + } + #ForgotPasswordMessage { + width: 456px; + } + .ForgotPasswordButtonsContainer { + margin-top: 10px; + width: 456px; + } +} + +/* FORGOT PASSWORD SHARED */ +#forgot-email { + &.not-found-error { + p.warning.invalid-email { + opacity: 0; + } + } + &.invalid-email { + p.warning.not-found-error { + opacity: 0; + } + } +} +p.warning { + margin: 0; + font-size: 12px; + line-height: 12px; + font-weight: 400; + font-style: italic; + color: #CC5F5A; + opacity: 0; + @include transition(opacity 0.2s, color 0.2s); +} +span.asterisk { + display: none; + font-size: 14px; + margin: 0; +} +.panel-error { + span.asterisk {display: inline;} + p.warning { + opacity: 1; + } + input { + border-color: #E0B4B4; + background-color: #FFF6F6; + } + label { + color: #CC5F5A; + } +} + +/* Loading icon */ +.button.ghostery-button { + .loader { + display: none; + } + &.loading { + pointer-events: none; + .loader { + display: inline-block; + height: 12px; + width: 12px; + } + .title { + display: none; + } + } + &:focus { + outline: 0; + + .loader:after { + background: #760176; + } + } +} + +.loader { + font-size: 10px; + text-indent: -9999em; + width: 10px; + height: 10rem; + border-radius: 50%; + background: #ffffff; + background: -moz-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); + background: -webkit-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); + background: -o-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); + background: -ms-linear-gradient(left, #ffffff 10%, rgba(255, 255, 255, 0) 42%); + background: linear-gradient(to right, #ffffff 10%, rgba(255, 255, 255, 0) 42%); + position: relative; + -webkit-animation: load3 1.4s infinite linear; + animation: load3 1.4s infinite linear; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); +} +.loader:before { + width: 50%; + height: 50%; + background: #ffffff; + border-radius: 100% 0 0 0; + position: absolute; + top: 0; + left: 0; + content: ''; +} +.loader:after { + background: #930194; + width: 75%; + height: 75%; + border-radius: 50%; + content: ''; + margin: auto; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; +} + +@-webkit-keyframes load3 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@keyframes load3 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} +@media only screen and (max-width: 740px) { + .ForgotPasswordView__header { + flex-direction: column; + } +} diff --git a/app/panel/containers/ForgotPasswordContainer.js b/app/shared-components/ForgotPassword/ForgotPasswordContainer.js similarity index 88% rename from app/panel/containers/ForgotPasswordContainer.js rename to app/shared-components/ForgotPassword/ForgotPasswordContainer.js index 92e71accc..0add4b2dc 100644 --- a/app/panel/containers/ForgotPasswordContainer.js +++ b/app/shared-components/ForgotPassword/ForgotPasswordContainer.js @@ -13,8 +13,9 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; -import ForgotPassword from '../components/ForgotPassword'; -import * as actions from '../actions/PanelActions'; // get shared actions from Panel +import ForgotPassword from './ForgotPassword'; +import * as actions from '../../panel/actions/PanelActions'; // get shared actions from Panel +import { setToast } from '../../hub/Views/AppView/AppViewActions'; import { resetPassword } from '../../Account/AccountActions'; /** * Map redux store state properties to ForgotPassword component own properties. @@ -33,7 +34,7 @@ const mapStateToProps = () => Object.assign({}); * @param {Object} ownProps ForgotPassword component own props * @return {function} to be used as an argument in redux connect call */ -const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(Object.assign(actions, { resetPassword }), dispatch) }); +const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(Object.assign(actions, { resetPassword, setToast }), dispatch) }); /** * Connect ForgotPassword component to the Redux store. * @memberOf PanelContainers diff --git a/app/shared-components/index.js b/app/shared-components/index.js index 8cea5f1ae..dcc550c36 100644 --- a/app/shared-components/index.js +++ b/app/shared-components/index.js @@ -22,6 +22,7 @@ import SteppedNavigation from './SteppedNavigation'; import ToastMessage from './ToastMessage'; import ToggleCheckbox from './ToggleCheckbox'; import ToggleSwitch from './ToggleSwitch'; +import ForgotPassword from './ForgotPassword/ForgotPasswordContainer'; export { ExitButton, @@ -30,5 +31,6 @@ export { SteppedNavigation, ToastMessage, ToggleCheckbox, - ToggleSwitch + ToggleSwitch, + ForgotPassword };