Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip - from 94683 - remove input lock and rely on canceling pending change #94787

Draft
wants to merge 1 commit into
base: update/email-verification-form-and-notice
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { get, includes } from 'lodash';
import PropTypes from 'prop-types';
import { Component } from 'react';
import { connect } from 'react-redux';
import { emailFormEventEmitter } from 'calypso/me/account/account-email-field';
import {
verifyEmail,
resetVerifyEmailState,
Expand Down Expand Up @@ -67,15 +66,14 @@ class VerifyEmailDialog extends Component {
if ( this.props.currentRoute !== changeEmailRoute ) {
return <a href="/me/account" />;
}
// If we are already on /me/account, close the dialog and dispatch a signal to focus the input.
// If we are already on /me/account, close the dialog.
return (
<Button
borderless
plain
compact
onClick={ () => {
this.handleClose();
emailFormEventEmitter?.dispatchEvent( new Event( 'unlockEmailInput' ) );
} }
/>
);
Expand Down
123 changes: 9 additions & 114 deletions client/me/account/account-email-field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FormInputValidation, FormLabel } from '@automattic/components';
import { Button } from '@wordpress/components';
import emailValidator from 'email-validator';
import { useTranslate } from 'i18n-calypso';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
import QueryAllDomains from 'calypso/components/data/query-all-domains';
import FormFieldset from 'calypso/components/forms/form-fieldset';
import FormSettingExplanation from 'calypso/components/forms/form-setting-explanation';
Expand Down Expand Up @@ -94,13 +94,7 @@ const AccountEmailValidationNotice = ( {
return <FormInputValidation isError text={ noticeText } />;
};

const EmailFieldExplanationText = ( {
setIsLockedInput,
focusInput,
}: {
setIsLockedInput: React.Dispatch< React.SetStateAction< boolean > >;
focusInput: () => void;
} ) => {
const EmailFieldExplanationText = () => {
const dispatch = useDispatch();
const translate = useTranslate();
const domainsList: ResponseDomain[] = useSelector( getFlatDomainsList );
Expand All @@ -113,21 +107,6 @@ const EmailFieldExplanationText = ( {

const editContactInfoInBulkUrl = `/domains/manage?site=all&action=edit-contact-email`;

const unlockWrapper = (
<Button
className="account-email-field__enable-input"
variant="link"
onClick={ ( ev: React.MouseEvent< HTMLButtonElement > ) => {
ev.preventDefault();
setIsLockedInput( false );
// Ensure input is focused when the user clicks
// this, regardless of if it was previously
// locked or not.
focusInput();
} }
/>
);

const cancelWrapper = (
<Button
className="account-email-field__enable-input"
Expand All @@ -143,22 +122,19 @@ const EmailFieldExplanationText = ( {
if ( isRequestingDomainList || ! hasCustomDomainRegistration ) {
// Show unverified message and cancel pending change option.
return translate(
'Your email has not been verified yet. Need to update your address? {{unlockWrapper}}Click here to update it{{/unlockWrapper}}.{{br/}} To cancel the pending email change {{cancelWrapper}}click here{{/cancelWrapper}}.',
'Your email has not been verified yet. To cancel the pending email change {{cancelWrapper}}click here{{/cancelWrapper}}.',
{
components: {
unlockWrapper,
br: <br />,
cancelWrapper,
},
}
);
}
// Show unverified message, domain contact info message, and cancel pending change option.
return translate(
'Your email has not been verified yet. Need to update your address? {{unlockWrapper}}Click here to update it{{/unlockWrapper}}.{{br/}} Update contact information on your domain names if necessary {{link}}here{{/link}}.{{br/}} To cancel the pending email change {{cancelWrapper}}click here{{/cancelWrapper}}.',
'Your email has not been verified yet. To cancel the pending email change {{cancelWrapper}}click here{{/cancelWrapper}}.{{br/}} Update contact information on your domain names if necessary {{link}}here{{/link}}.',
{
components: {
unlockWrapper,
br: <br />,
cancelWrapper,
link: <a href={ editContactInfoInBulkUrl } />,
Expand All @@ -169,29 +145,13 @@ const EmailFieldExplanationText = ( {

if ( ! isEmailVerified ) {
// Show unverified message.
return translate(
'Your email has not been verified yet. Need to update your address? {{unlockWrapper}}Click here to update it{{/unlockWrapper}}.',
{
components: {
unlockWrapper,
},
}
);
return translate( 'Your email has not been verified yet.' );
}

// Standard message.
return translate(
'Not publicly displayed, except to owners of sites you subscribe to. {{unlockWrapper}}Click here to update it{{/unlockWrapper}}.',
{
components: {
unlockWrapper,
},
}
);
return translate( 'Not publicly displayed, except to owners of sites you subscribe to.' );
};

export const emailFormEventEmitter = new EventTarget();

const AccountEmailField = ( {
emailInputId = 'user_email',
emailInputName = 'user_email',
Expand All @@ -205,20 +165,6 @@ const AccountEmailField = ( {
const dispatch = useDispatch();
const translate = useTranslate();
const isEmailChangePending = useSelector( isPendingEmailChange );
const inputRef = useRef< FormTextInput >( null );
const [ isLockedInput, setIsLockedInput ] = useState( true );
// We manage emailSettingToShow in state to prevent jarring UX and jumping input value in edge
// cases. Our settings state clears unsaved settings when they are equal to saved settings. Ex.
// The user has a pending email change so new_user_email is shown initially. When they edit this
// input, it reflects the unsaved value of user_email. The user then types in the value of their
// previously verified email (user_email). The unsaved user_email is reset to null in settings
// state since it is the same as the saved setting. If we were calculating this
// emailSettingToShow value on every render, the input would jarringly jump from the value the
// user typed in back to the pending email value (new_user_email). Since we manage this in
// state and with effects, it remains more stable in interaction.
const [ emailSettingToShow, setEmailSettingToShow ] = useState(
isEmailChangePending ? 'new_user_email' : 'user_email'
);
const [ emailInvalidReason, setEmailInvalidReason ] = useState< AccountEmailValidationReason >(
EMAIL_VALIDATION_REASON_IS_VALID
);
Expand All @@ -230,29 +176,8 @@ const AccountEmailField = ( {
};
}, [ dispatch ] );

// If the isEmailChangePending updates to true, show the new email address field. This may
// happen just after initial load, as the selector may return null at first. Or this may happen
// after the user saves the form with an updated email address.
useEffect( () => {
if ( isEmailChangePending ) {
setEmailSettingToShow( 'new_user_email' );
} else {
// Similarly ensure this resets when there is no longer a pending change (ex. user
// cancels pending change)
setEmailSettingToShow( 'user_email' );
}
}, [ isEmailChangePending ] );

// Once the user starts editing, ensure we show the user_email field since that is the one being
// updated in unsavedUserSettings.
useEffect( () => {
if ( unsavedUserSettings.user_email ) {
setEmailSettingToShow( 'user_email' );
}
}, [ unsavedUserSettings.user_email, setEmailSettingToShow ] );

const emailAddress = getUserSetting( {
settingName: emailSettingToShow,
settingName: isEmailChangePending ? 'new_user_email' : 'user_email',
unsavedUserSettings,
userSettings,
} );
Expand All @@ -276,46 +201,19 @@ const AccountEmailField = ( {
dispatch( setUserSetting( 'user_email', value ) );
};

const focusInput = () => {
inputRef.current?.focus();
};

// Ensure input is focused when it is triggered to unlock.
useEffect( () => {
if ( ! isLockedInput ) {
focusInput();
}
}, [ isLockedInput ] );

// Allow unlocking the email field from an external source such as the email verification dialog
// when it appears on this page.
useEffect( () => {
const unlockHandler = () => {
setIsLockedInput( false );
focusInput();
};

emailFormEventEmitter.addEventListener( 'unlockEmailInput', unlockHandler );

return () => {
emailFormEventEmitter.removeEventListener( 'unlockEmailInput', unlockHandler );
};
}, [] );

return (
<>
<QueryAllDomains />
<FormFieldset>
<FormLabel htmlFor={ emailInputId }>{ translate( 'Email address' ) }</FormLabel>
<FormTextInput
disabled={ isEmailControlDisabled || isLockedInput }
disabled={ isEmailControlDisabled || isEmailChangePending }
id={ emailInputId }
name={ emailInputName }
isError={ emailInvalidReason !== EMAIL_VALIDATION_REASON_IS_VALID }
onFocus={ onFocus }
value={ emailAddress }
onChange={ onEmailAddressChange }
ref={ inputRef }
/>

<AccountEmailValidationNotice
Expand All @@ -325,10 +223,7 @@ const AccountEmailField = ( {
/>

<FormSettingExplanation>
<EmailFieldExplanationText
setIsLockedInput={ setIsLockedInput }
focusInput={ focusInput }
/>
<EmailFieldExplanationText />
</FormSettingExplanation>
</FormFieldset>
</>
Expand Down
7 changes: 6 additions & 1 deletion client/me/security-account-email/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useDispatch, useSelector } from 'calypso/state';
import { errorNotice, successNotice } from 'calypso/state/notices/actions';
import getUnsavedUserSettings from 'calypso/state/selectors/get-unsaved-user-settings';
import getUserSettings from 'calypso/state/selectors/get-user-settings';
import isPendingEmailChange from 'calypso/state/selectors/is-pending-email-change';
import { saveUnsavedUserSettings } from 'calypso/state/user-settings/thunks';
import type { NoticeId, NoticeOptions } from 'calypso/state/notices/types';
import type { CalypsoDispatch } from 'calypso/state/types';
Expand All @@ -33,6 +34,8 @@ const SecurityAccountEmail = ( { path }: { path: string } ) => {
const emailValidationHandler = ( emailIsValid: boolean ): void =>
setIsNewEmailValid( emailIsValid );

const emailChangeIsPending = useSelector( isPendingEmailChange );

const unsavedUserSettings = useSelector( getUnsavedUserSettings ) ?? {};
const userSettings = useSelector( getUserSettings ) ?? {};

Expand Down Expand Up @@ -109,7 +112,9 @@ const SecurityAccountEmail = ( { path }: { path: string } ) => {

<Button
busy={ isSubmittingUpdate }
disabled={ isSubmittingUpdate || ! isEmailModified || ! isNewEmailValid }
disabled={
isSubmittingUpdate || emailChangeIsPending || ! isEmailModified || ! isNewEmailValid
}
onClick={ submitEmailUpdate }
primary
>
Expand Down
Loading