Skip to content

Commit

Permalink
Move calls for email magic link to data-layer (#38866)
Browse files Browse the repository at this point in the history
* Add  data-layer hander for email login

* update send-login-email usage in get-apps

* update usage to sendMobileEmailLogin

* try removing dialog

* add option to toggle global notices and add option for custom success and error events

* remove custom error and success dispatches

* Fix no undefined types linter issue

* Use globalThis on jsdoc

* Try dispatching all the actions!

* move recordTracksEventWithClientId calls to data-layer handler

* make sure the right type of email is sent

* refactor to make usage clear between mobile app and normal email login

* add jsdoc

Co-authored-by: Miguel Torres <miguelmariatorresrojas@gmail.com>
  • Loading branch information
gwwar and mmtr authored Jan 20, 2020
1 parent 28e871d commit 5365eac
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 139 deletions.
13 changes: 3 additions & 10 deletions client/blocks/app-promo/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import Gridicon from 'components/gridicon';
*/
import { localize } from 'i18n-calypso';
import { recordTracksEvent } from 'state/analytics/actions';
import wpcom from 'lib/wp';
import { Dialog } from '@automattic/components';
import { fetchUserSettings } from 'state/user-settings/actions';
import getUserSettings from 'state/selectors/get-user-settings';
import { sendEmailLogin } from 'state/auth/actions';

/**
* Image dependencies
Expand Down Expand Up @@ -110,16 +110,9 @@ export class AppPromo extends React.Component {

sendMagicLink = () => {
this.recordClickEvent();

const email = this.props.userSettings.user_email;
wpcom.undocumented().requestMagicLoginEmail( {
email,
infer: true,
scheme: 'wordpress',
} );

this.props.sendEmailLogin( email, { showGlobalNotices: false, isMobileAppLogin: true } );
this.onShowDialog();

return false;
};

Expand Down Expand Up @@ -230,5 +223,5 @@ export default connect(
state => ( {
userSettings: getUserSettings( state ),
} ),
{ fetchUserSettings, recordTracksEvent }
{ fetchUserSettings, recordTracksEvent, sendEmailLogin }
)( localize( AppPromo ) );
37 changes: 9 additions & 28 deletions client/blocks/get-apps/mobile-download-card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Card, Button } from '@automattic/components';
import QuerySmsCountries from 'components/data/query-countries/sms';
import FormPhoneInput from 'components/forms/form-phone-input';
import getCountries from 'state/selectors/get-countries';
import { infoNotice, successNotice, errorNotice } from 'state/notices/actions';
import { successNotice, errorNotice } from 'state/notices/actions';
import { fetchUserSettings } from 'state/user-settings/actions';
import { accountRecoverySettingsFetch } from 'state/account-recovery/settings/actions';
import {
Expand All @@ -26,11 +26,11 @@ import {
import getUserSettings from 'state/selectors/get-user-settings';
import hasUserSettings from 'state/selectors/has-user-settings';
import { http } from 'state/data-layer/wpcom-http/actions';
import wpcom from 'lib/wp';
import phoneValidation from 'lib/phone-validation';
import userAgent from 'lib/user-agent';
import twoStepAuthorization from 'lib/two-step-authorization';
import { recordTracksEvent } from 'state/analytics/actions';
import { recordTracksEvent, withAnalytics } from 'state/analytics/actions';
import { sendEmailLogin } from 'state/auth/actions';

function sendSMS( phone ) {
function onSuccess( dispatch ) {
Expand All @@ -53,30 +53,6 @@ function sendSMS( phone ) {
} );
}

function sendMagicLink( email ) {
//Actions must be plain objects. Use custom middleware for async actions.
// https://stackoverflow.com/questions/46765896/react-redux-actions-must-be-plain-objects-use-custom-middleware-for-async-acti
return function( dispatch ) {
const duration = { duration: 4000 };
dispatch( infoNotice( i18n.translate( 'Sending email' ), duration ) );

return wpcom
.undocumented()
.requestMagicLoginEmail( {
email,
infer: true,
scheme: 'wordpress',
} )
.then( () => {
dispatch( successNotice( i18n.translate( 'Email Sent. Check your mail app!' ), duration ) );
} )
.catch( error => {
dispatch( errorNotice( i18n.translate( 'Sorry, we couldn’t send the email.' ), duration ) );
return Promise.reject( error );
} );
};
}

class MobileDownloadCard extends React.Component {
static propTypes = {
translate: PropTypes.func,
Expand Down Expand Up @@ -316,12 +292,17 @@ class MobileDownloadCard extends React.Component {
};

onSubmitLink = () => {
this.props.recordTracksEvent( 'calypso_get_apps_magic_link_button_click' );
const email = this.props.userSettings.user_email;
this.props.sendMagicLink( email );
};
}

const sendMagicLink = email =>
withAnalytics(
recordTracksEvent( 'calypso_get_apps_magic_link_button_click' ),
sendEmailLogin( email, { showGlobalNotices: true, isMobileAppLogin: true } )
);

export default connect(
state => ( {
countriesList: getCountries( state, 'sms' ),
Expand Down
23 changes: 7 additions & 16 deletions client/blocks/login/login-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import config from 'config';
import FormsButton from 'components/forms/form-button';
import FormInputValidation from 'components/forms/form-input-validation';
import Divider from './divider';
import { fetchMagicLoginRequestEmail } from 'state/login/magic-login/actions';
import FormPasswordInput from 'components/forms/form-password-input';
import FormTextInput from 'components/forms/form-text-input';
import getCurrentQueryArguments from 'state/selectors/get-current-query-arguments';
Expand Down Expand Up @@ -52,12 +51,13 @@ import Notice from 'components/notice';
import SocialLoginForm from './social';
import { localizeUrl } from 'lib/i18n-utils';
import TextControl from 'extensions/woocommerce/components/text-control';
import { sendEmailLogin } from 'state/auth/actions';

export class LoginForm extends Component {
static propTypes = {
accountType: PropTypes.string,
disableAutoFocus: PropTypes.bool,
fetchMagicLoginRequestEmail: PropTypes.func.isRequired,
sendEmailLogin: PropTypes.func.isRequired,
formUpdate: PropTypes.func.isRequired,
getAuthAccountType: PropTypes.func.isRequired,
hasAccountTypeLoaded: PropTypes.bool.isRequired,
Expand Down Expand Up @@ -131,19 +131,10 @@ export class LoginForm extends Component {
}

if ( ! this.props.hasAccountTypeLoaded && isPasswordlessAccount( nextProps.accountType ) ) {
this.props.recordTracksEvent( 'calypso_login_block_login_form_send_magic_link' );

this.props
.fetchMagicLoginRequestEmail( this.state.usernameOrEmail, nextProps.redirectTo )
.then( () => {
this.props.recordTracksEvent( 'calypso_login_block_login_form_send_magic_link_success' );
} )
.catch( error => {
this.props.recordTracksEvent( 'calypso_login_block_login_form_send_magic_link_failure', {
error_code: error.error,
error_message: error.message,
} );
} );
this.props.sendEmailLogin( this.state.usernameOrEmail, {
redirectTo: nextProps.redirectTo,
loginFormFlow: true,
} );

page( login( { isNative: true, twoFactorAuthType: 'link' } ) );
}
Expand Down Expand Up @@ -638,7 +629,7 @@ export default connect(
};
},
{
fetchMagicLoginRequestEmail,
sendEmailLogin,
formUpdate,
getAuthAccountType,
loginUser,
Expand Down
33 changes: 5 additions & 28 deletions client/lib/wpcom-undocumented/lib/undocumented.js
Original file line number Diff line number Diff line change
Expand Up @@ -1430,29 +1430,6 @@ Undocumented.prototype.usersEmailVerification = function( query, fn ) {
return this.wpcom.req.post( args, fn );
};

/**
* Request a "Magic Login" email be sent to a user so they can use it to log in
*
* @param {object} data - object containing an email address
* @param {Function} fn - Function to invoke when request is complete
* @returns {Promise} promise
*/
Undocumented.prototype.requestMagicLoginEmail = function( data, fn ) {
restrictByOauthKeys( data );

data.locale = getLocaleSlug();
data.lang_id = getLanguage( data.locale ).value;

return this.wpcom.req.post(
'/auth/send-login-email',
{
apiVersion: '1.2',
},
data,
fn
);
};

/**
* Create a new site
*
Expand Down Expand Up @@ -2142,9 +2119,9 @@ Undocumented.prototype.exportReaderFeed = function( fn ) {
* Imports given XML file into the user's Reader feed.
* XML file is expected to be in OPML format.
*
* @param {File} file The File object to upload
* @param {globalThis.File} file The File object to upload
* @param {Function} fn The callback function
* @returns {XMLHttpRequest} The XHR instance, to attach `progress`
* @returns {globalThis.XMLHttpRequest} The XHR instance, to attach `progress`
* listeners to, etc.
*/
Undocumented.prototype.importReaderFeed = function( file, fn ) {
Expand All @@ -2168,7 +2145,7 @@ Undocumented.prototype.importReaderFeed = function( file, fn ) {
* @param {string} deviceFamily The device family
* @param {string} deviceName The device name
* @param {Function} fn The callback function
* @returns {XMLHttpRequest} The XHR instance
* @returns {globalThis.XMLHttpRequest} The XHR instance
*/
Undocumented.prototype.registerDevice = function( registration, deviceFamily, deviceName, fn ) {
debug( '/devices/new' );
Expand All @@ -2189,7 +2166,7 @@ Undocumented.prototype.registerDevice = function( registration, deviceFamily, de
*
* @param {number} deviceId The device ID for the registration to be removed
* @param {Function} fn The callback function
* @returns {XMLHttpRequest} The XHR instance
* @returns {globalThis.XMLHttpRequest} The XHR instance
*/
Undocumented.prototype.unregisterDevice = function( deviceId, fn ) {
debug( '/devices/:device_id/delete' );
Expand All @@ -2213,7 +2190,7 @@ Undocumented.prototype.wordAdsApprove = function( siteId ) {
*
* @param {number} siteId -- the ID of the site
* @param {string} [plugin] -- .org plugin slug
* @param {File} [theme] -- theme zip to upload
* @param {globalThis.File} [theme] -- theme zip to upload
* @param {Function} [onProgress] -- called with upload progress status
*
* @returns {Promise} promise for handling result
Expand Down
27 changes: 8 additions & 19 deletions client/login/magic-login/request-login-email-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ import { defer, get } from 'lodash';
/**
* Internal dependencies
*/
import {
fetchMagicLoginRequestEmail,
hideMagicLoginRequestNotice,
} from 'state/login/magic-login/actions';
import { hideMagicLoginRequestNotice } from 'state/login/magic-login/actions';
import getMagicLoginCurrentView from 'state/selectors/get-magic-login-current-view';
import getMagicLoginRequestedEmailSuccessfully from 'state/selectors/get-magic-login-requested-email-successfully';
import getMagicLoginRequestEmailError from 'state/selectors/get-magic-login-request-email-error';
Expand All @@ -31,6 +28,7 @@ import LoggedOutForm from 'components/logged-out-form';
import Notice from 'components/notice';
import { localize } from 'i18n-calypso';
import { getCurrentUser } from 'state/current-user/selectors';
import { sendEmailLogin } from 'state/auth/actions';

class RequestLoginEmailForm extends React.Component {
static propTypes = {
Expand All @@ -44,7 +42,7 @@ class RequestLoginEmailForm extends React.Component {
userEmail: PropTypes.string,

// mapped to dispatch
fetchMagicLoginRequestEmail: PropTypes.func.isRequired,
sendEmailLogin: PropTypes.func.isRequired,
hideMagicLoginRequestNotice: PropTypes.func.isRequired,
};

Expand Down Expand Up @@ -85,19 +83,10 @@ class RequestLoginEmailForm extends React.Component {
return;
}

this.props.recordTracksEvent( 'calypso_login_email_link_submit' );

this.props
.fetchMagicLoginRequestEmail( usernameOrEmail, this.props.redirectTo )
.then( () => {
this.props.recordTracksEvent( 'calypso_login_email_link_success' );
} )
.catch( error => {
this.props.recordTracksEvent( 'calypso_login_email_link_failure', {
error_code: error.error,
error_message: error.message,
} );
} );
this.props.sendEmailLogin( usernameOrEmail, {
redirectTo: this.props.redirectTo,
requestLoginEmailFormFlow: true,
} );
};

getUsernameOrEmailFromState() {
Expand Down Expand Up @@ -200,7 +189,7 @@ const mapState = state => {
};

const mapDispatch = {
fetchMagicLoginRequestEmail,
sendEmailLogin,
hideMagicLoginRequestNotice,
recordTracksEvent,
};
Expand Down
1 change: 1 addition & 0 deletions client/state/action-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ export const LOGIN_AUTH_ACCOUNT_TYPE_REQUESTING = 'LOGIN_AUTH_ACCOUNT_TYPE_REQUE
export const LOGIN_AUTH_ACCOUNT_TYPE_REQUEST_FAILURE = 'LOGIN_AUTH_ACCOUNT_TYPE_REQUEST_FAILURE';
export const LOGIN_AUTH_ACCOUNT_TYPE_REQUEST_SUCCESS = 'LOGIN_AUTH_ACCOUNT_TYPE_REQUEST_SUCCESS';
export const LOGIN_AUTH_ACCOUNT_TYPE_RESET = 'LOGIN_AUTH_ACCOUNT_TYPE_RESET';
export const LOGIN_EMAIL_SEND = 'LOGIN_EMAIL_SEND';
export const LOGIN_FORM_UPDATE = 'LOGIN_FORM_UPDATE';
export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_REQUEST_FAILURE = 'LOGIN_REQUEST_FAILURE';
Expand Down
47 changes: 47 additions & 0 deletions client/state/auth/actions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Internal dependencies
*/
import { LOGIN_EMAIL_SEND } from 'state/action-types';
import { getLanguage, getLocaleSlug } from 'lib/i18n-utils';
import 'state/data-layer/wpcom/auth/send-login-email';

/**
* Sends an email with a link that allows a user to login WordPress.com or the native apps
*
* @param {string} email - email to send to
* @param {object} options object:
* {string} redirectTo - url to redirect to after login
* {boolean} loginFormFlow - if true, dispatches actions associated with passwordless login
* {boolean} requestLoginEmailFormFlow - if true, dispatches actions associated with email me login
* {boolean} isMobileAppLogin - if true, will send an email that allows login to the native apps
* {boolean} showGlobalNotices - if true, displays global notices to user about the email
*
* @returns {object} action object
*/
export const sendEmailLogin = (
email,
{
redirectTo,
showGlobalNotices = false,
loginFormFlow = false,
requestLoginEmailFormFlow = false,
isMobileAppLogin = false,
}
) => {
//Kind of weird usage, but this is a straight port from undocumented.js for now.
//I can move this to the caller, if there's equivalent info in the state tree
const locale = getLocaleSlug();
const lang_id = getLanguage( locale ).value;

return {
type: LOGIN_EMAIL_SEND,
email,
locale,
lang_id,
redirect_to: redirectTo,
isMobileAppLogin,
showGlobalNotices,
loginFormFlow,
requestLoginEmailFormFlow,
};
};
Loading

0 comments on commit 5365eac

Please sign in to comment.