From 20eebd689ce9ed7344b5adfe5b08f76e1a49667b Mon Sep 17 00:00:00 2001 From: Omar Alshaker Date: Tue, 4 Feb 2025 18:36:06 +0100 Subject: [PATCH] Revert "Domains: Implement multi-target email forwards (#98837)" This reverts commit a1abc2cf0cc591c6740c0b20da268815dc74e312. --- client/data/emails/types.ts | 31 --- .../emails/use-add-email-forward-mutation.tsx | 27 +-- .../use-remove-email-forward-mutation.tsx | 21 +- ...e-resend-verify-email-forward-mutation.tsx | 16 +- .../has-duplicated-email-forwards.js | 7 + client/lib/domains/email-forwarding/index.js | 35 +++ .../email-forwarding/actions-menu/index.tsx | 80 ------- .../email-forwarding/actions-menu/style.scss | 6 - .../email-forwarding-add-new-compact-list.tsx | 90 ++++++-- .../email-forwarding-add-new-compact.jsx | 191 ++++++++++++++++ .../email-forwards-list/index.tsx | 168 -------------- .../email-forwards-list/style.scss | 68 ------ .../my-sites/email/email-forwarding/hooks.ts | 46 ---- .../email/email-forwarding/style.scss | 12 + .../verification-notices/index.tsx | 28 --- .../verification-notices/style.scss | 40 ---- .../add-new-form/destination-input.tsx | 75 ------- .../email-forwards-add/add-new-form/index.tsx | 51 ----- .../add-new-form/source-input.tsx | 37 --- .../add-new-form/styles.scss | 96 -------- .../email-forwards-add/add-new-form/types.ts | 24 -- .../email-forwards-add/add-new-form/utils.tsx | 60 ----- .../email/email-forwards-add/index.tsx | 4 + .../email/email-forwards-add/style.scss | 5 + .../home/email-mailbox-action-menu.jsx | 41 +++- .../home/email-mailbox-warnings.jsx | 99 ++++++++ .../home/email-plan-mailboxes-list.tsx | 43 +++- .../email-forward-header.tsx | 34 +++ .../email-forward-secondary-details.tsx | 22 ++ .../email-forward-target.tsx | 24 -- .../home/email-plan/index.jsx | 1 + .../email/email-management/style.scss | 212 ++++++++++++++---- .../src/confirmation-dialog/index.tsx | 49 ---- .../src/confirmation-dialog/style.scss | 22 -- packages/components/src/index.ts | 1 - 35 files changed, 754 insertions(+), 1012 deletions(-) create mode 100644 client/lib/domains/email-forwarding/has-duplicated-email-forwards.js delete mode 100644 client/my-sites/email/email-forwarding/actions-menu/index.tsx delete mode 100644 client/my-sites/email/email-forwarding/actions-menu/style.scss create mode 100644 client/my-sites/email/email-forwarding/email-forwarding-add-new-compact.jsx delete mode 100644 client/my-sites/email/email-forwarding/email-forwards-list/index.tsx delete mode 100644 client/my-sites/email/email-forwarding/email-forwards-list/style.scss delete mode 100644 client/my-sites/email/email-forwarding/hooks.ts create mode 100644 client/my-sites/email/email-forwarding/style.scss delete mode 100644 client/my-sites/email/email-forwarding/verification-notices/index.tsx delete mode 100644 client/my-sites/email/email-forwarding/verification-notices/style.scss delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/destination-input.tsx delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/index.tsx delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/source-input.tsx delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/styles.scss delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/types.ts delete mode 100644 client/my-sites/email/email-forwards-add/add-new-form/utils.tsx create mode 100644 client/my-sites/email/email-management/home/email-mailbox-warnings.jsx create mode 100644 client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-header.tsx create mode 100644 client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-secondary-details.tsx delete mode 100644 client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-target.tsx delete mode 100644 packages/components/src/confirmation-dialog/index.tsx delete mode 100644 packages/components/src/confirmation-dialog/style.scss diff --git a/client/data/emails/types.ts b/client/data/emails/types.ts index 7722f718839472..64c0b15c53ee98 100644 --- a/client/data/emails/types.ts +++ b/client/data/emails/types.ts @@ -9,36 +9,6 @@ export type EmailAccountEmail = { warnings: Warning[]; }; -export type ResponseError = { - error: - | 'destination_failed' - | 'invalid_input' - | 'not_valid_destination' - | 'destination_failed' - | 'too_many_destinations' - | 'exceeded_mailbox_forwards' - | 'mailbox_too_long' - | 'not_valid_mailbox' - | 'empty_destination' - | 'same_destination_domain' - | 'forward_exists'; - message: - | string - | { - error_message: string; - /** - * The index of the faulty email address in the `destinations` array - */ - index: number; - }; -}; - -export type AlterDestinationParams = { - mailbox: string; - destination: string; - domain: string; -}; - type EmailAccountDomain = { domain: string; is_primary: boolean; @@ -75,5 +45,4 @@ export type Mailbox = { mailbox: string; warnings?: Warning[]; temporary?: boolean; - target: string; }; diff --git a/client/data/emails/use-add-email-forward-mutation.tsx b/client/data/emails/use-add-email-forward-mutation.tsx index 253975c671b2a4..dc3cdd17abd9ec 100644 --- a/client/data/emails/use-add-email-forward-mutation.tsx +++ b/client/data/emails/use-add-email-forward-mutation.tsx @@ -8,13 +8,10 @@ import { useDispatch, useSelector } from 'calypso/state'; import { errorNotice } from 'calypso/state/notices/actions'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; import { getCacheKey as getEmailAccountsQueryKey } from './use-get-email-accounts-query'; -import type { ResponseError } from './types'; import type { UseMutationOptions } from '@tanstack/react-query'; -const ArrayOfFive = new Array( 5 ); - type AddMailboxFormData = { - destinations: typeof ArrayOfFive; + destination: string; mailbox: string; }; @@ -41,7 +38,7 @@ export function useIsLoading() { export default function useAddEmailForwardMutation( domainName: string, mutationOptions: Omit< - UseMutationOptions< any, ResponseError, AddMailboxFormData, Context >, + UseMutationOptions< any, unknown, AddMailboxFormData, Context >, 'mutationFn' > = {} ) { @@ -68,7 +65,7 @@ export default function useAddEmailForwardMutation( }; mutationOptions.onMutate = async ( variables ) => { - const { mailbox, destinations } = variables; + const { mailbox, destination } = variables; suppliedOnMutate?.( variables ); await queryClient.cancelQueries( { queryKey: emailAccountsQueryKey } ); @@ -82,16 +79,16 @@ export default function useAddEmailForwardMutation( const newEmailForwards = orderBy( [ ...emailForwards, - ...destinations.map( ( d ) => ( { + { domain: domainName, email_type: 'email_forward', is_verified: false, mailbox, role: 'standard', - target: d, + target: destination, temporary: true, warnings: [], - } ) ), + }, ], [ 'mailbox' ], [ 'asc' ] @@ -138,7 +135,7 @@ export default function useAddEmailForwardMutation( }; }; - mutationOptions.onError = ( error: ResponseError, variables, context ) => { + mutationOptions.onError = ( error, variables, context ) => { suppliedOnError?.( error, variables, context ); if ( context ) { @@ -166,14 +163,12 @@ export default function useAddEmailForwardMutation( ); if ( error ) { - const message = - typeof error.message === 'object' ? error.message.error_message : error.message; errorMessage = translate( 'Failed to add email forward for {{strong}}%(emailAddress)s{{/strong}} with message "%(message)s". Please try again or {{contactSupportLink}}contact support{{/contactSupportLink}}.', { args: { emailAddress: variables.mailbox, - message, + message: error as string, }, components: noticeComponents, } @@ -183,11 +178,11 @@ export default function useAddEmailForwardMutation( dispatch( errorNotice( errorMessage ) ); }; - return useMutation< any, ResponseError, AddMailboxFormData, Context >( { - mutationFn: ( { mailbox, destinations } ) => + return useMutation< any, unknown, AddMailboxFormData, Context >( { + mutationFn: ( { mailbox, destination } ) => wp.req.post( `/domains/${ encodeURIComponent( domainName ) }/email/new`, { mailbox, - destinations, + destination, } ), ...mutationOptions, } ); diff --git a/client/data/emails/use-remove-email-forward-mutation.tsx b/client/data/emails/use-remove-email-forward-mutation.tsx index 572bcb9dfbc767..6c6e4985aedc0b 100644 --- a/client/data/emails/use-remove-email-forward-mutation.tsx +++ b/client/data/emails/use-remove-email-forward-mutation.tsx @@ -8,7 +8,7 @@ import { useDispatch, useSelector } from 'calypso/state'; import { errorNotice } from 'calypso/state/notices/actions'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; import { getCacheKey as getEmailAccountsQueryKey } from './use-get-email-accounts-query'; -import type { EmailAccountEmail, AlterDestinationParams, Mailbox } from './types'; +import type { EmailAccountEmail } from './types'; import type { UseMutationOptions } from '@tanstack/react-query'; type Context = { @@ -26,7 +26,7 @@ const MUTATION_KEY = 'removeEmailForward'; export default function useRemoveEmailForwardMutation( domainName: string, mutationOptions: Omit< - UseMutationOptions< any, unknown, AlterDestinationParams, Context >, + UseMutationOptions< any, unknown, EmailAccountEmail, Context >, 'mutationFn' > = {} ) { @@ -70,11 +70,7 @@ export default function useRemoveEmailForwardMutation( { ...previousEmailAccountsQueryData.accounts[ 0 ], emails: emailForwards.filter( - ( forward: EmailAccountEmail ) => - ! ( - forward.mailbox === emailForward.mailbox && - forward.target === emailForward.destination - ) + ( forward: EmailAccountEmail ) => forward.mailbox !== emailForward.mailbox ), }, ], @@ -144,14 +140,13 @@ export default function useRemoveEmailForwardMutation( dispatch( errorMessage ); }; - return useMutation< Mailbox, unknown, AlterDestinationParams, Context >( { - mutationFn: ( { mailbox, destination } ) => { - return wp.req.post( + return useMutation< any, unknown, EmailAccountEmail, Context >( { + mutationFn: ( { mailbox } ) => + wp.req.post( `/domains/${ encodeURIComponent( domainName ) }/email/${ encodeURIComponent( mailbox - ) }/${ encodeURIComponent( destination ) }/delete` - ); - }, + ) }/delete` + ), ...mutationOptions, } ); } diff --git a/client/data/emails/use-resend-verify-email-forward-mutation.tsx b/client/data/emails/use-resend-verify-email-forward-mutation.tsx index 0d3fa010471e42..b6075d2f10ae94 100644 --- a/client/data/emails/use-resend-verify-email-forward-mutation.tsx +++ b/client/data/emails/use-resend-verify-email-forward-mutation.tsx @@ -1,10 +1,11 @@ import { CALYPSO_CONTACT } from '@automattic/urls'; import { useMutation } from '@tanstack/react-query'; import { useTranslate } from 'i18n-calypso'; +import { getEmailForwardAddress } from 'calypso/lib/emails'; import wp from 'calypso/lib/wp'; import { useDispatch } from 'calypso/state'; import { errorNotice, successNotice } from 'calypso/state/notices/actions'; -import type { AlterDestinationParams } from './types'; +import type { EmailAccountEmail } from './types'; import type { UseMutationOptions } from '@tanstack/react-query'; const MUTATION_KEY = 'reverifyEmailForward'; @@ -17,10 +18,7 @@ const MUTATION_KEY = 'reverifyEmailForward'; */ export default function useResendVerifyEmailForwardMutation( domainName: string, - mutationOptions: Omit< - UseMutationOptions< any, unknown, AlterDestinationParams >, - 'mutationFn' - > = {} + mutationOptions: Omit< UseMutationOptions< any, unknown, EmailAccountEmail >, 'mutationFn' > = {} ) { const dispatch = useDispatch(); const translate = useTranslate(); @@ -33,7 +31,7 @@ export default function useResendVerifyEmailForwardMutation( mutationOptions.onSuccess = ( data, emailForward, context ) => { suppliedOnSuccess?.( data, emailForward, context ); - const { destination } = emailForward; + const destination = getEmailForwardAddress( emailForward ); const successMessage = translate( 'Successfully sent confirmation email for %(email)s to %(destination)s.', @@ -71,12 +69,12 @@ export default function useResendVerifyEmailForwardMutation( dispatch( errorNotice( failureMessage ) ); }; - return useMutation< any, unknown, AlterDestinationParams >( { - mutationFn: ( { mailbox, destination } ) => + return useMutation< any, unknown, EmailAccountEmail >( { + mutationFn: ( { mailbox } ) => wp.req.post( `/domains/${ encodeURIComponent( domainName ) }/email/${ encodeURIComponent( mailbox - ) }/${ encodeURIComponent( destination ) }/resend-verification` + ) }/resend-verification` ), ...mutationOptions, } ); diff --git a/client/lib/domains/email-forwarding/has-duplicated-email-forwards.js b/client/lib/domains/email-forwarding/has-duplicated-email-forwards.js new file mode 100644 index 00000000000000..092b6982f7a32e --- /dev/null +++ b/client/lib/domains/email-forwarding/has-duplicated-email-forwards.js @@ -0,0 +1,7 @@ +/** + * @param newEmailForward a string representing a new email forward + * @returns { boolean } If the email forward is present in the existing email forwards collection + */ +export function hasDuplicatedEmailForwards( newEmailForward, existingEmailForwards ) { + return existingEmailForwards?.some( ( forward ) => forward.mailbox === newEmailForward ); +} diff --git a/client/lib/domains/email-forwarding/index.js b/client/lib/domains/email-forwarding/index.js index a6267c5c667998..fb5f9feea70411 100644 --- a/client/lib/domains/email-forwarding/index.js +++ b/client/lib/domains/email-forwarding/index.js @@ -1,3 +1,38 @@ +import emailValidator from 'email-validator'; +import { mapValues } from 'lodash'; +import { hasDuplicatedEmailForwards } from 'calypso/lib/domains/email-forwarding/has-duplicated-email-forwards'; + +function validateAllFields( fieldValues, existingEmailForwards = [] ) { + return mapValues( fieldValues, ( value, fieldName ) => { + const isValid = validateField( { + value, + name: fieldName, + } ); + + if ( ! isValid ) { + return [ 'Invalid' ]; + } + + if ( fieldName !== 'mailbox' ) { + return []; + } + + return hasDuplicatedEmailForwards( value, existingEmailForwards ) ? [ 'Duplicated' ] : []; + } ); +} + +function validateField( { name, value } ) { + switch ( name ) { + case 'mailbox': + return /^[a-z0-9._+-]{1,64}$/i.test( value ) && ! /(^\.)|(\.{2,})|(\.$)/.test( value ); + case 'destination': + return emailValidator.validate( value ); + default: + return true; + } +} + export { getEmailForwardsCount } from './get-email-forwards-count'; export { hasEmailForwards } from './has-email-forwards'; export { getDomainsWithEmailForwards } from './get-domains-with-email-forwards'; +export { validateAllFields }; diff --git a/client/my-sites/email/email-forwarding/actions-menu/index.tsx b/client/my-sites/email/email-forwarding/actions-menu/index.tsx deleted file mode 100644 index e35d2e405a6eb4..00000000000000 --- a/client/my-sites/email/email-forwarding/actions-menu/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { ConfirmationDialog } from '@automattic/components'; -import { - __experimentalHeading as Heading, - __experimentalText as Text, - __experimentalVStack as VStack, - DropdownMenu, -} from '@wordpress/components'; -import { rotateLeft, trash, moreHorizontalMobile } from '@wordpress/icons'; -import { useTranslate } from 'i18n-calypso'; -import { useState } from 'react'; -import { getEmailForwardAddress } from 'calypso/lib/emails'; -import { useResend, useRemove } from '../hooks'; -import type { Mailbox } from '../../../../data/emails/types'; -import './style.scss'; - -export const ActionsMenu = ( { mailbox }: { mailbox: Mailbox } ) => { - const [ isOpen, setIsOpen ] = useState( false ); - const remove = useRemove( { mailbox } ); - const resend = useResend( { mailbox } ); - const translate = useTranslate(); - - const handleConfirm = () => { - remove( mailbox.mailbox, mailbox.domain, getEmailForwardAddress( mailbox ) ); - setIsOpen( false ); - }; - - const handleCancel = () => { - setIsOpen( false ); - }; - - return ( - <> - - - - { translate( 'Are you sure you want to remove this email forward?' ) } - - - { translate( - "This will remove it from our records and if it's not used in another forward, it will require reverification if added again." - ) } - - - - - resend( mailbox.mailbox, mailbox.domain, getEmailForwardAddress( mailbox ) ), - }, - { - title: 'Remove', - icon: trash, - onClick: () => setIsOpen( true ), - }, - ] - : [ - { - title: 'Remove', - icon: trash, - onClick: () => setIsOpen( true ), - }, - ] - } - /> - - ); -}; diff --git a/client/my-sites/email/email-forwarding/actions-menu/style.scss b/client/my-sites/email/email-forwarding/actions-menu/style.scss deleted file mode 100644 index d359afb720d0c5..00000000000000 --- a/client/my-sites/email/email-forwarding/actions-menu/style.scss +++ /dev/null @@ -1,6 +0,0 @@ -// Delete me once https://github.com/Automattic/wp-calypso/pull/98095#discussion_r1937657191 is addressed. -.email-forward-list__actions { - .components-dropdown.components-dropdown-menu { - flex-grow: unset; - } -} \ No newline at end of file diff --git a/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact-list.tsx b/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact-list.tsx index 3922269a0167af..779b644ac5eaa6 100644 --- a/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact-list.tsx +++ b/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact-list.tsx @@ -1,12 +1,16 @@ +import { Button } from '@automattic/components'; +import { useTranslate } from 'i18n-calypso'; +import { Fragment, useState } from 'react'; import useAddEmailForwardMutation from 'calypso/data/emails/use-add-email-forward-mutation'; import { useGetEmailAccountsQuery } from 'calypso/data/emails/use-get-email-accounts-query'; -import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; +import { validateAllFields } from 'calypso/lib/domains/email-forwarding'; +import EmailForwardingAddNewCompact from 'calypso/my-sites/email/email-forwarding/email-forwarding-add-new-compact'; import { useSelector } from 'calypso/state'; import { getSelectedSiteId } from 'calypso/state/ui/selectors'; -import { NewForwardForm } from '../email-forwards-add/add-new-form'; import type { FormEvent } from 'react'; type Props = { + onBeforeAddEmailForwards?: () => void; onAddedEmailForwards: () => void; selectedDomainName: string; showFormHeader?: boolean; @@ -14,20 +18,31 @@ type Props = { const EmailForwardingAddNewCompactList = ( { onAddedEmailForwards, + onBeforeAddEmailForwards, selectedDomainName, + showFormHeader, }: Props ) => { + const translate = useTranslate(); + + const [ emailForwards, setEmailForwards ] = useState( [ + { destination: '', mailbox: '', isValid: false }, + ] ); + const selectedSiteId = useSelector( getSelectedSiteId ); const { data: emailAccounts = [] } = useGetEmailAccountsQuery( selectedSiteId, selectedDomainName ); - const existingEmailForwards = emailAccounts[ 0 ]?.emails ?? []; const { mutate: addEmailForward, isPending: isAddingEmailForward } = useAddEmailForwardMutation( selectedDomainName ); + const hasValidEmailForwards = () => { + return ! emailForwards?.some( ( forward ) => ! forward.isValid ); + }; + const submitNewEmailForwards = ( event: FormEvent< HTMLFormElement > ) => { event.preventDefault(); @@ -35,29 +50,68 @@ const EmailForwardingAddNewCompactList = ( { return; } - const data = new FormData( event.currentTarget ); - const mailbox = data.get( 'mailbox' ) as string; - const destinations = data.getAll( 'destinations' ); + onBeforeAddEmailForwards?.(); - recordTracksEvent( 'calypso_email_management_email_forwarding_add', { - destinations_count: destinations.length, - mailbox: mailbox, - domain: selectedDomainName, + emailForwards?.map( ( { mailbox, destination } ) => { + addEmailForward( { mailbox, destination } ); } ); - addEmailForward( { mailbox, destinations } ); - onAddedEmailForwards?.(); }; + const onAddNewEmailForward = () => { + setEmailForwards( ( prev ) => { + return [ ...prev, { destination: '', mailbox: '', isValid: false } ]; + } ); + }; + + const onRemoveEmailForward = ( index: number ) => { + const newEmailForwards = [ ...emailForwards ]; + newEmailForwards.splice( index, 1 ); + setEmailForwards( newEmailForwards ); + }; + + const onUpdateEmailForward = ( + index: number, + name: 'destination' | 'mailbox', + value: string + ) => { + const newEmailForwards = [ ...emailForwards ]; + newEmailForwards[ index ][ name ] = value; + + const validEmailForward = validateAllFields( newEmailForwards[ index ], existingEmailForwards ); + newEmailForwards[ index ].isValid = + validEmailForward.mailbox.length === 0 && validEmailForward.destination.length === 0; + + setEmailForwards( newEmailForwards ); + }; + return (
-
- + { emailForwards.map( ( fields, index ) => ( + +
+ i !== index ), + ] } + fields={ fields } + index={ index } + onAddEmailForward={ onAddNewEmailForward } + onRemoveEmailForward={ onRemoveEmailForward } + onUpdateEmailForward={ onUpdateEmailForward } + selectedDomainName={ selectedDomainName } + showFormHeader={ showFormHeader } + /> +
+
+ ) ) } + +
+
); diff --git a/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact.jsx b/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact.jsx new file mode 100644 index 00000000000000..bcc14ae48dec6c --- /dev/null +++ b/client/my-sites/email/email-forwarding/email-forwarding-add-new-compact.jsx @@ -0,0 +1,191 @@ +import { Button, FormInputValidation, FormLabel, Gridicon } from '@automattic/components'; +import { localize } from 'i18n-calypso'; +import PropTypes from 'prop-types'; +import { Component } from 'react'; +import CardHeading from 'calypso/components/card-heading'; +import FormButton from 'calypso/components/forms/form-button'; +import FormFieldset from 'calypso/components/forms/form-fieldset'; +import FormTextInput from 'calypso/components/forms/form-text-input'; +import FormTextInputWithAffixes from 'calypso/components/forms/form-text-input-with-affixes'; +import { validateAllFields } from 'calypso/lib/domains/email-forwarding'; +import formState from 'calypso/lib/form-state'; +class EmailForwardingAddNewCompact extends Component { + static propTypes = { + fields: PropTypes.object, + index: PropTypes.number, + onAddEmailForward: PropTypes.func.isRequired, + onRemoveEmailForward: PropTypes.func.isRequired, + selectedDomainName: PropTypes.string.isRequired, + onUpdateEmailForward: PropTypes.func.isRequired, + emailForwards: PropTypes.array, + showFormHeader: PropTypes.bool, + }; + + isMounted = false; + + constructor( props ) { + super( props ); + + this.state = { + fields: this.props.fields, + }; + + this.formStateController = formState.Controller( { + initialFields: this.getInitialFields(), + onNewState: this.setFormState, + validatorFunction: ( fieldValues, onComplete ) => { + onComplete( null, validateAllFields( fieldValues, this.props.emailForwards ?? [] ) ); + }, + } ); + } + + componentDidMount() { + this.isMounted = true; + } + + componentWillUnmount() { + this.isMounted = false; + } + + getInitialFields() { + return this.props.fields; + } + + setFormState = ( fields ) => { + if ( this.isMounted ) { + this.setState( { fields } ); + } + }; + + renderAddButton() { + const { onAddEmailForward, onButtonClick, translate } = this.props; + return ( +
+ + + +
+ ); + } + + renderRemoveButton() { + const updateForm = () => { + this.props.onRemoveEmailForward( this.props.index ); + }; + + return ( + updateForm() }> + + { this.props.translate( 'Remove this forward' ) } + + ); + } + + renderFormFields() { + const { translate, selectedDomainName, index, fields, showFormHeader } = this.props; + const isValidMailbox = this.isValid( 'mailbox' ); + const isValidDestination = this.isValid( 'destination' ); + const { mailbox, destination } = fields; + const mailboxError = this.getError( 'mailbox' ); + const destinationError = this.getError( 'destination' ); + + return ( +
+ { showFormHeader ? ( + { translate( 'New email forwarding address' ) } + ) : null } + + { translate( 'Emails sent to' ) } + this.onChange( event, index ) } + isError={ ! isValidMailbox } + suffix={ '@' + selectedDomainName } + value={ mailbox } + /> + { ! isValidMailbox && } + + + + { translate( 'Will be forwarded to this email address' ) } + this.onChange( event, index ) } + isError={ ! isValidDestination } + value={ destination } + /> + { ! isValidDestination && } + +
+ ); + } + + render() { + return ( + <> + { this.renderFormFields() } + { this.props.index > 0 ? this.renderRemoveButton() : null } + + ); + } + + onChange = ( event, index ) => { + const { name } = event.target; + let { value } = event.target; + + value = value.replace( /\s/g, '' ); + if ( name === 'mailbox' ) { + // Removes the domain part + value = value.replace( /@.*$/, '' ); + } + + this.formStateController.handleFieldChange( { + name, + value, + } ); + + this.props.onUpdateEmailForward( index, name, value ); + }; + + isValid( fieldName ) { + return ! formState.isFieldInvalid( this.state.fields, fieldName ); + } + + getError( fieldName ) { + const { translate } = this.props; + const errorMessage = formState.getFieldErrorMessages( this.state.fields, fieldName ); + if ( ! errorMessage ) { + return null; + } + + if ( fieldName === 'mailbox' ) { + if ( errorMessage.filter( ( t ) => t === 'Invalid' ).length === 1 ) { + return translate( 'Only numbers, letters, dashes, underscores, and periods are allowed.' ); + } + + if ( errorMessage.filter( ( t ) => t === 'Duplicated' ).length === 1 ) { + return translate( 'Please use unique mailboxes' ); + } + } + + if ( fieldName === 'destination' ) { + if ( errorMessage.filter( ( t ) => t === 'Invalid' ).length === 1 ) { + return translate( 'Invalid email address' ); + } + } + + return null; + } +} + +export default localize( EmailForwardingAddNewCompact ); diff --git a/client/my-sites/email/email-forwarding/email-forwards-list/index.tsx b/client/my-sites/email/email-forwarding/email-forwards-list/index.tsx deleted file mode 100644 index a5edd3f7846db7..00000000000000 --- a/client/my-sites/email/email-forwarding/email-forwards-list/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useMediaQuery } from '@wordpress/compose'; -import { useTranslate } from 'i18n-calypso'; -import InfoPopover from 'calypso/components/info-popover'; -import { Mailbox } from 'calypso/data/emails/types'; -import { getEmailAddress } from 'calypso/lib/emails'; -import EmailForwardTarget from '../../email-management/home/email-plan-mailboxes/email-forward-target'; -import { ActionsMenu } from '../actions-menu'; -import { VerificationPendingNotice } from '../verification-notices'; -import './style.scss'; - -function groupByMailbox( mailboxes: Mailbox[] ) { - return Object.entries( - mailboxes.reduce( - ( groups, mailbox ) => { - const mailboxGroup = groups[ mailbox.mailbox ] || []; - mailboxGroup.push( mailbox ); - groups[ mailbox.mailbox ] = mailboxGroup; - return groups; - }, - {} as Record< string, Mailbox[] > - ) - ); -} - -/** - * Truncates helloIndifferentWorld to hello...rld. - * @param str the long string - */ -function smartTruncate( str: string ) { - if ( str.length <= 32 ) { - return str; - } - const start = str.slice( 0, 10 ); - const end = str.slice( -5 ); - return `${ start }…${ end }`; -} - -function smartTruncateEmail( str: string ) { - const [ localPart, domain ] = str.split( '@' ); - return `${ smartTruncate( localPart ) }@${ smartTruncate( domain ) }`; -} - -function THead() { - const translate = useTranslate(); - const isMobile = useMediaQuery( '(max-width: 960px)' ); - - if ( isMobile ) { - return ( - - - { translate( 'Mailbox' ) } - { translate( 'Status' ) } - -
{ translate( 'Actions' ) }
- - - - ); - } - return ( - - - { translate( 'Mailbox' ) } - { translate( 'To' ) } - { translate( 'Status' ) } - -
{ translate( 'Actions' ) }
- - - - ); -} - -function TBody( { mailbox, targets }: { mailbox: string; targets: Mailbox[] } ) { - const isMobile = useMediaQuery( '(max-width: 960px)' ); - const fromAddress = getEmailAddress( targets[ 0 ] ); - - if ( isMobile ) { - return ( - - - - { smartTruncateEmail( fromAddress ) } - - - { targets.map( ( mailbox ) => ( - - - - - - { mailbox.warnings?.length ? ( - - - - ) : ( - - ) } - - -
- -
- - - ) ) } - - ); - } - return ( - - { targets.map( ( mailbox, index ) => ( - - { index === 0 ? smartTruncateEmail( fromAddress ) : null } - - - - - - - -
- -
- - - ) ) } - - ); -} - -export function EmailForwardsList( { - mailboxes, - actionPath, -}: { - mailboxes: Mailbox[]; - actionPath: string | undefined; -} ) { - const translate = useTranslate(); - const normalizedMailboxes = groupByMailbox( mailboxes ); - - return ( - <> -
-

{ translate( 'Email forwards' ) }

- { actionPath && ( - - ) } -
- - - { normalizedMailboxes.map( ( [ from, targets ] ) => { - return ; - } ) } -
- - ); -} diff --git a/client/my-sites/email/email-forwarding/email-forwards-list/style.scss b/client/my-sites/email/email-forwarding/email-forwards-list/style.scss deleted file mode 100644 index 948868ff331b47..00000000000000 --- a/client/my-sites/email/email-forwarding/email-forwards-list/style.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.email-forwards-list__header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 24px 0; - - margin: 0 10px; - - @include break-large { - margin: 0; - } - - h2 { - font-size: 1rem; - font-weight: 500; - } - - -} - -.email-forward-list { - border: 1px solid #E0E0E0; - background: var(--color-surface); - margin: 0 10px; - width: calc(100% - 20px); - - @include break-large { - margin: 0; - width: 100%; - } - - td, - th { - padding: 8px 16px; - font-weight: normal; - font-size: $font-body-small; - vertical-align: middle; - } - - thead th { - padding: 16px; - } - - - tbody>tr:first-child>td { - border-top: 1px solid #E0E0E0; - padding-top: 16px; - } - - tbody>tr:last-child>td { - padding-bottom: 16px; - } - - .info-popover { - svg.gridicon.gridicons-info-outline { - color: var(--studio-orange-50); - } - } -} - -div.email-forward-list__actions { - display: flex; - gap: 1rem; - justify-content: end; -} \ No newline at end of file diff --git a/client/my-sites/email/email-forwarding/hooks.ts b/client/my-sites/email/email-forwarding/hooks.ts deleted file mode 100644 index 38e5533dda611a..00000000000000 --- a/client/my-sites/email/email-forwarding/hooks.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useCallback } from 'react'; -import useRemoveEmailForwardMutation from 'calypso/data/emails/use-remove-email-forward-mutation'; -import useResendVerifyEmailForwardMutation from 'calypso/data/emails/use-resend-verify-email-forward-mutation'; -import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; -import type { Mailbox } from 'calypso/data/emails/types'; - -export function useRemove( { mailbox }: { mailbox: Mailbox } ) { - const { mutate: removeEmailForward } = useRemoveEmailForwardMutation( mailbox.domain ); - - return useCallback( - ( mailbox: string, domain: string, destination: string ) => { - recordTracksEvent( 'calypso_email_management_email_forwarding_delete_click', { - destination, - domain_name: domain, - mailbox: mailbox, - } ); - - removeEmailForward( { - mailbox: mailbox, - destination, - domain, - } ); - }, - [ removeEmailForward ] - ); -} - -export function useResend( { mailbox }: { mailbox: Mailbox } ) { - const { mutate: resendVerificationEmail } = useResendVerifyEmailForwardMutation( mailbox.domain ); - - return useCallback( - ( mailbox: string, domain: string, destination: string ) => { - recordTracksEvent( - 'calypso_email_management_email_forwarding_resend_verification_email_click', - { - destination, - domain_name: domain, - mailbox: mailbox, - } - ); - - resendVerificationEmail( { mailbox, destination, domain } ); - }, - [ resendVerificationEmail ] - ); -} diff --git a/client/my-sites/email/email-forwarding/style.scss b/client/my-sites/email/email-forwarding/style.scss new file mode 100644 index 00000000000000..4a828eb4a67d41 --- /dev/null +++ b/client/my-sites/email/email-forwarding/style.scss @@ -0,0 +1,12 @@ +.email-forwarding__add-new { + .email-forwarding__form-content { + border-top: 1px solid var(--color-neutral-0); + overflow: visible; + padding-top: 20px; + } +} + +.email-forwarding-add-new-compact-list__actions { + display: flex; + justify-content: flex-end; +} diff --git a/client/my-sites/email/email-forwarding/verification-notices/index.tsx b/client/my-sites/email/email-forwarding/verification-notices/index.tsx deleted file mode 100644 index 4d45cb6c322486..00000000000000 --- a/client/my-sites/email/email-forwarding/verification-notices/index.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Icon } from '@wordpress/components'; -import { check, info } from '@wordpress/icons'; -import { useTranslate } from 'i18n-calypso'; -import { EMAIL_WARNING_SLUG_UNVERIFIED_FORWARDS } from 'calypso/lib/emails/email-provider-constants'; -import { Mailbox } from '../../../../data/emails/types'; -import './style.scss'; - -export function VerificationPendingNotice( { mailbox }: { mailbox: Mailbox } ) { - const translate = useTranslate(); - const hasWarning = mailbox.warnings?.some( - ( warning ) => warning.warning_slug === EMAIL_WARNING_SLUG_UNVERIFIED_FORWARDS - ); - - if ( ! hasWarning && ! mailbox.temporary ) { - return ( -
- - { translate( 'Active' ) } -
- ); - } - return ( -
- - { translate( 'Pending verification' ) } -
- ); -} diff --git a/client/my-sites/email/email-forwarding/verification-notices/style.scss b/client/my-sites/email/email-forwarding/verification-notices/style.scss deleted file mode 100644 index 3d440845c734dc..00000000000000 --- a/client/my-sites/email/email-forwarding/verification-notices/style.scss +++ /dev/null @@ -1,40 +0,0 @@ -@import "@wordpress/base-styles/breakpoints"; -@import "@wordpress/base-styles/mixins"; - -.email-forward-verification-status { - display: flex; - align-items: center; - gap: 5px; - - &.active { - svg { - fill: var(--studio-green-50); - } - - // hide text on mobile. - span { - display: none; - } - - @include break-large { - span { - display: inline-block; - } - } - } - - &.verification-pending { - color: var(--studio-orange-50); - - svg { - fill: var(--studio-orange-50); - display: none; - } - - @include break-large { - svg { - display: flex; - } - } - } -} \ No newline at end of file diff --git a/client/my-sites/email/email-forwards-add/add-new-form/destination-input.tsx b/client/my-sites/email/email-forwards-add/add-new-form/destination-input.tsx deleted file mode 100644 index fd5ffadc5752bf..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/destination-input.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { FormTokenField, Notice } from '@wordpress/components'; -import { useTranslate } from 'i18n-calypso'; -import React from 'react'; -import { isValidDestination } from './utils'; -import type { DestinationsInputProps, ValidationError } from './types'; -import type { TokenItem } from '@wordpress/components/build-types/form-token-field/types'; -import './styles.scss'; - -const MAX_FORWARD_DESTINATIONS = 5; - -export function DestinationsInput( props: DestinationsInputProps ) { - const { values, onChange, selectedDomainName, disabled, existingForwardsForMailbox, mailbox } = - props; - /** Consider existing forwards in the limit. */ - const limit = MAX_FORWARD_DESTINATIONS - existingForwardsForMailbox.length; - const translate = useTranslate(); - const [ error, setError ] = React.useState< ValidationError | null >( null ); - - if ( limit === 0 ) { - return ( - - { translate( 'This mailbox is already forwarded to the maximum number of destinations.' ) } - - ); - } - - function handleChange( newValues: Array< string | TokenItem > ) { - onChange( ( newValues as string[] ).map( ( el ) => el.toLowerCase().trim() ) ); - } - - // This mailbox is already forwarded to the maximum number of destinations. - if ( limit === 0 ) { - return null; - } - - return ( - <> - setError( null ) } - __experimentalValidateInput={ ( value ) => { - const error = isValidDestination( - value, - selectedDomainName, - mailbox, - existingForwardsForMailbox, - translate - ); - if ( typeof error === 'object' ) { - setError( error ); - return false; - } - return error; - } } - placeholder={ translate( - 'These are the target email addresses where your emails will be forwarded.' - ) } - /> - { values.map( ( value ) => ( - - ) ) } - { error && ( - - { error.message } - - ) } - - ); -} diff --git a/client/my-sites/email/email-forwards-add/add-new-form/index.tsx b/client/my-sites/email/email-forwards-add/add-new-form/index.tsx deleted file mode 100644 index f3ea3d847913d7..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Button } from '@wordpress/components'; -import { useTranslate } from 'i18n-calypso'; -import React from 'react'; -import { DestinationsInput } from './destination-input'; -import { SourceInput } from './source-input'; -import { isValidMailbox } from './utils'; -import type { NewForwardFormProps } from './types'; -import './styles.scss'; - -export function NewForwardForm( { - selectedDomainName, - existingEmailForwards, - disabled, -}: NewForwardFormProps ) { - const translate = useTranslate(); - const [ mailbox, setMailbox ] = React.useState( '' ); - const [ destinations, setDestinations ] = React.useState< string[] >( [] ); - - const existingForwardsForMailbox = existingEmailForwards?.filter( - ( forward ) => - forward.mailbox.localeCompare( mailbox, undefined, { sensitivity: 'base' } ) === 0 - ); - - return ( -
- - -
- -
-
- ); -} diff --git a/client/my-sites/email/email-forwards-add/add-new-form/source-input.tsx b/client/my-sites/email/email-forwards-add/add-new-form/source-input.tsx deleted file mode 100644 index 2fb91ff793d3f0..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/source-input.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { TextControl } from '@wordpress/components'; -import clsx from 'clsx'; -import { useTranslate } from 'i18n-calypso'; -import React from 'react'; -import type { SourceInputProps } from './types'; -import './styles.scss'; - -export function SourceInput( props: SourceInputProps ) { - const { onChange, suffix, ...rest } = props; - const translate = useTranslate(); - const [ highlightSuffix, setHighlightSuffix ] = React.useState( 0 ); - - return ( -
- onChange( value.replace( /@.*/gi, '' ) ) } - onKeyUp={ ( event ) => { - if ( event.key === '@' ) { - setHighlightSuffix( ( s ) => s + 1 ); - } - } } - { ...rest } - /> - { /* Blink the suffix when the user enters @ */ } -

- { suffix } -

-
- ); -} diff --git a/client/my-sites/email/email-forwards-add/add-new-form/styles.scss b/client/my-sites/email/email-forwards-add/add-new-form/styles.scss deleted file mode 100644 index fc1b5f2c7c0297..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/styles.scss +++ /dev/null @@ -1,96 +0,0 @@ -.email-forwarding__form-content { - display: flex; - flex-direction: column; - gap: 24px; - padding-top: 24px; - - div.components-base-control__field { - margin-bottom: 0; - } -} - -.email-forwarding__mailbox-input-wrapper { - position: relative; - - // These margins mess our suffix. - div.components-base-control__field { - margin-bottom: 0; - } - - & div.components-base-control.email-forwarding__mailbox-input { - margin-right: 0 !important; - } - - & input { - // Make sure the suffix doesn't cross the input border. - background: transparent; - z-index: 2; - position: relative; - } - - - .email-forwarding__mailbox-suffix { - margin: 0; - position: absolute; - right: 1px; - bottom: 1px; - padding: 5px 10px; - background: var(--studio-gray-0); - border-top: 1px solid var(--studio-gray-0); - border-left: 1px solid var(--studio-gray-10); - color: var(--studio-gray-50); - font-size: $font-body-small; - z-index: 1; - - &.animate { - animation: blink 3s forwards; - - @keyframes blink { - 5% { - opacity: 0; - } - - 10% { - opacity: 1; - } - - 15% { - opacity: 0; - } - - 20% { - opacity: 1; - } - } - } - } -} - - - -/* If the user didn't add any tokens, show a shimmer effect, because they may not know to press Enter. */ -.components-form-token-field:not(:has(.components-form-token-field__token)):focus-within { - .components-form-token-field__help { - animation: shimmer 15s infinite; - background-size: 110%; - color: transparent; - background-color: var(--studio-gray-50); - background-image: linear-gradient(90deg, var(--studio-gray-50) 0%, var(--studio-white) 5%, var(--studio-gray-50) 10%); - background-position: 110% 0; - background-clip: text; - - @keyframes shimmer { - 0% { - background-position: 110% 0; - } - - 80% { - background-position: 110% 0; - } - - 100% { - background-position: -320% 0; - } - } - } -} \ No newline at end of file diff --git a/client/my-sites/email/email-forwards-add/add-new-form/types.ts b/client/my-sites/email/email-forwards-add/add-new-form/types.ts deleted file mode 100644 index 00cbe22b0386c3..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { EmailAccountEmail } from 'calypso/data/emails/types'; - -export interface DestinationsInputProps { - values: string[]; - onChange: ( values: string[] ) => void; - selectedDomainName: string; - disabled: boolean; - existingForwardsForMailbox: EmailAccountEmail[]; - mailbox: string; -} - -export interface SourceInputProps { - suffix: string; - disabled: boolean; - onChange: ( value: string ) => void; - value: string; -} - -export type ValidationError = { severity: 'warning' | 'error'; message: string }; -export interface NewForwardFormProps { - selectedDomainName: string; - existingEmailForwards: EmailAccountEmail[]; - disabled: boolean; -} diff --git a/client/my-sites/email/email-forwards-add/add-new-form/utils.tsx b/client/my-sites/email/email-forwards-add/add-new-form/utils.tsx deleted file mode 100644 index 111b11ae006b9b..00000000000000 --- a/client/my-sites/email/email-forwards-add/add-new-form/utils.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { sprintf } from '@wordpress/i18n'; -import emailValidator from 'email-validator'; -import type { ValidationError } from './types'; -import type { EmailAccountEmail } from 'calypso/data/emails/types'; - -export function isValidMailbox( mailbox: string ) { - const allowedSpecialChars = "!#$%&'*+/=?^_`{|}~."; - - if ( mailbox.length === 0 || mailbox.length > 64 ) { - return false; - } - - if ( mailbox.length > 64 ) { - return false; - } - - for ( const char of mailbox ) { - if ( ! /[a-zA-Z0-9]/.test( char ) && ! allowedSpecialChars.includes( char ) ) { - return false; - } - } - - if ( /\.{2,}/.test( mailbox ) || mailbox.startsWith( '.' ) || mailbox.endsWith( '.' ) ) { - return false; - } - - return true; -} - -export function isValidDestination( - value: string, - selectedDomainName: string, - mailbox: string, - existingForwardsForMailbox: EmailAccountEmail[], - translate: ( key: string ) => string -): ValidationError | boolean { - const valid = emailValidator.validate( value ); - const duplicate = existingForwardsForMailbox.find( ( e ) => e.target === value ); - const sameDomain = value.endsWith( `@${ selectedDomainName }` ); - if ( valid ) { - if ( duplicate ) { - return { - severity: 'warning', - message: sprintf( - /* translators: %s: email address %s: email address */ - translate( 'There is already a forward from (%1$s) to (%2$s).' ), - `${ mailbox }@${ selectedDomainName }`, - value - ), - }; - } - if ( sameDomain ) { - return { - severity: 'warning', - message: translate( 'You cannot forward to the same domain.' ), - }; - } - } - return valid; -} diff --git a/client/my-sites/email/email-forwards-add/index.tsx b/client/my-sites/email/email-forwards-add/index.tsx index 8abe00a9aeec06..e8d91daec33cde 100644 --- a/client/my-sites/email/email-forwards-add/index.tsx +++ b/client/my-sites/email/email-forwards-add/index.tsx @@ -35,6 +35,9 @@ type EmailForwardsAddProps = { showPageHeader?: boolean; }; +// eslint-disable-next-line @typescript-eslint/no-empty-function +const noop = (): void => {}; + const EmailForwardsAdd = ( { selectedDomainName, source, @@ -99,6 +102,7 @@ const EmailForwardsAdd = ( { { ! areDomainsLoading && ( diff --git a/client/my-sites/email/email-forwards-add/style.scss b/client/my-sites/email/email-forwards-add/style.scss index 88a2e170db9c1c..24d9d3447538df 100644 --- a/client/my-sites/email/email-forwards-add/style.scss +++ b/client/my-sites/email/email-forwards-add/style.scss @@ -1,5 +1,10 @@ .email-forwarding__add-new { margin-bottom: 28px; + + .email-forwarding__add-new .email-forwarding__form-content { + border-top: none; + padding-top: 0; + } } .email-forwards-add__placeholder { diff --git a/client/my-sites/email/email-management/home/email-mailbox-action-menu.jsx b/client/my-sites/email/email-management/home/email-mailbox-action-menu.jsx index 73521e7826a961..10afe9ace43d0f 100644 --- a/client/my-sites/email/email-management/home/email-mailbox-action-menu.jsx +++ b/client/my-sites/email/email-management/home/email-mailbox-action-menu.jsx @@ -14,9 +14,16 @@ import googleSlidesIcon from 'calypso/assets/images/email-providers/google-works import titanMailIcon from 'calypso/assets/images/email-providers/titan/services/flat/mail.svg'; import EllipsisMenu from 'calypso/components/ellipsis-menu'; import PopoverMenuItem from 'calypso/components/popover-menu/item'; +import useRemoveEmailForwardMutation from 'calypso/data/emails/use-remove-email-forward-mutation'; import { useRemoveTitanMailboxMutation } from 'calypso/data/emails/use-remove-titan-mailbox-mutation'; import { canCurrentUserAddEmail } from 'calypso/lib/domains'; -import { getEmailAddress, hasGoogleAccountTOSWarning, isEmailUserAdmin } from 'calypso/lib/emails'; +import { hasEmailForwards } from 'calypso/lib/domains/email-forwarding'; +import { + getEmailAddress, + getEmailForwardAddress, + hasGoogleAccountTOSWarning, + isEmailUserAdmin, +} from 'calypso/lib/emails'; import { getGmailUrl, getGoogleAdminUrl, @@ -170,6 +177,28 @@ const getGSuiteMenuItems = ( { account, mailbox, translate } ) => { ]; }; +const getEmailForwardMenuItems = ( { mailbox, removeEmailForward, translate } ) => { + return [ + { + isInternalLink: true, + materialIcon: 'delete', + onClick: () => { + recordTracksEvent( 'calypso_email_management_email_forwarding_delete_click', { + destination: getEmailForwardAddress( mailbox ), + domain_name: mailbox.domain, + mailbox: mailbox.mailbox, + } ); + + removeEmailForward( mailbox ); + }, + key: `remove_forward:${ mailbox.mailbox }`, + title: translate( 'Remove email forward', { + comment: 'Remove an email forward', + } ), + }, + ]; +}; + const RemoveTitanMailboxConfirmationDialog = ( { mailbox, visible, setVisible } ) => { const dispatch = useDispatch(); const translate = useTranslate(); @@ -279,6 +308,8 @@ const EmailMailboxActionMenu = ( { account, domain, mailbox } ) => { const [ removeTitanMailboxDialogVisible, setRemoveTitanMailboxDialogVisible ] = useState( false ); const domainHasTitanMailWithUs = hasTitanMailWithUs( domain ); + const { mutate: removeEmailForward } = useRemoveEmailForwardMutation( mailbox.domain ); + const getMenuItems = () => { if ( domainHasTitanMailWithUs ) { return getTitanMenuItems( { @@ -294,6 +325,14 @@ const EmailMailboxActionMenu = ( { account, domain, mailbox } ) => { return getGSuiteMenuItems( { account, mailbox, translate } ); } + if ( hasEmailForwards( domain ) ) { + return getEmailForwardMenuItems( { + mailbox, + removeEmailForward, + translate, + } ); + } + return null; }; diff --git a/client/my-sites/email/email-management/home/email-mailbox-warnings.jsx b/client/my-sites/email/email-management/home/email-mailbox-warnings.jsx new file mode 100644 index 00000000000000..953072d8e4ef38 --- /dev/null +++ b/client/my-sites/email/email-management/home/email-mailbox-warnings.jsx @@ -0,0 +1,99 @@ +import { Button, Gridicon } from '@automattic/components'; +import { useTranslate } from 'i18n-calypso'; +import PropTypes from 'prop-types'; +import useResendVerifyEmailForwardMutation from 'calypso/data/emails/use-resend-verify-email-forward-mutation'; +import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; +import { getEmailForwardAddress } from 'calypso/lib/emails'; +import { EMAIL_WARNING_SLUG_UNVERIFIED_FORWARDS } from 'calypso/lib/emails/email-provider-constants'; +import { isEmailForwardAccount } from 'calypso/lib/emails/is-email-forward-account'; + +const EmailMailboxWarningText = ( { text } ) => { + return ( +
+ + + { text } +
+ ); +}; + +const EmailMailboxWarningAction = ( { buttonText, isExternal, ...otherProps } ) => { + return ( +
+ +
+ ); +}; + +const EmailMailboxReverifyWarning = ( { mailbox, ctaProps } ) => { + const translate = useTranslate(); + + const { mutate: resendVerificationEmail } = useResendVerifyEmailForwardMutation( mailbox.domain ); + + const warningText = translate( 'Verification required' ); + const buttonText = translate( 'Resend verification email' ); + + return ( + <> + + { + const destination = getEmailForwardAddress( mailbox ); + + recordTracksEvent( + 'calypso_email_management_email_forwarding_resend_verification_email_click', + { + destination, + domain_name: mailbox.domain, + mailbox: mailbox.mailbox, + } + ); + + resendVerificationEmail( mailbox, destination ); + } } + { ...ctaProps } + /> + + ); +}; + +const EmailMailboxWarnings = ( { account, mailbox, ctaProps } ) => { + if ( ! mailbox?.warnings?.length ) { + return null; + } + + return ( + <> + { mailbox.warnings.map( ( warning, index ) => { + const warningKey = `${ mailbox.mailbox }@${ mailbox.domain }-${ warning.warning_slug }-${ index }`; + + if ( isEmailForwardAccount( account ) ) { + if ( warning.warning_slug === EMAIL_WARNING_SLUG_UNVERIFIED_FORWARDS ) { + return ( + + ); + } + } + + return null; + } ) } + + ); +}; + +EmailMailboxWarnings.propTypes = { + account: PropTypes.object.isRequired, + mailbox: PropTypes.object.isRequired, + ctaProps: PropTypes.object, +}; + +export default EmailMailboxWarnings; diff --git a/client/my-sites/email/email-management/home/email-plan-mailboxes-list.tsx b/client/my-sites/email/email-management/home/email-plan-mailboxes-list.tsx index a2d5db05c46181..f3ff1f1d336326 100644 --- a/client/my-sites/email/email-management/home/email-plan-mailboxes-list.tsx +++ b/client/my-sites/email/email-management/home/email-plan-mailboxes-list.tsx @@ -1,4 +1,5 @@ import { Badge, MaterialIcon } from '@automattic/components'; +import { useDesktopBreakpoint } from '@automattic/viewport-react'; import { useTranslate } from 'i18n-calypso'; import Notice from 'calypso/components/notice'; import { isRecentlyRegistered } from 'calypso/lib/domains/utils'; @@ -7,8 +8,10 @@ import { EMAIL_ACCOUNT_TYPE_FORWARD } from 'calypso/lib/emails/email-provider-co import { getGSuiteSubscriptionStatus, hasGSuiteWithUs } from 'calypso/lib/gsuite'; import { hasTitanMailWithUs } from 'calypso/lib/titan'; import EmailMailboxActionMenu from 'calypso/my-sites/email/email-management/home/email-mailbox-action-menu'; +import EmailMailboxWarnings from 'calypso/my-sites/email/email-management/home/email-mailbox-warnings'; import EmailPlanWarnings from 'calypso/my-sites/email/email-management/home/email-plan-warnings'; -import { EmailForwardsList } from '../../email-forwarding/email-forwards-list'; +import EmailForwardHeader from './email-plan-mailboxes/email-forward-header'; +import EmailForwardSecondaryDetails from './email-plan-mailboxes/email-forward-secondary-details'; import EmailUpgradeNotice from './email-plan-mailboxes/email-upgrade-notice'; import MailboxListHeader from './email-plan-mailboxes/list-header'; import MailboxListItem from './email-plan-mailboxes/list-item'; @@ -28,7 +31,6 @@ type Props = { purchaseNewEmailAccountPath?: string; isLoadingEmails: boolean; }; - function EmailPlanMailboxesList( { context, domain, @@ -41,6 +43,7 @@ function EmailPlanMailboxesList( { const translate = useTranslate(); const accountType = account?.account_type; + const isDesktopResolution = useDesktopBreakpoint(); const isNoMailboxes = ! mailboxes || mailboxes.length < 1; const isAccountWarningPresent = !! account?.warnings.length; const isGoogleConfiguring = @@ -84,16 +87,37 @@ function EmailPlanMailboxesList( { function MailboxItems() { return mailboxes.map( ( mailbox ) => { + const mailboxHasWarnings = Boolean( mailbox?.warnings?.length ); + const showErrorStyling = context === 'email' && mailboxHasWarnings; + return ( <> - +
+ { context === 'email' && }
+ { ( context === 'domains' || context === 'hosting-overview' ) && ( +
+ + { mailboxHasWarnings && ( +
+ +
+ ) } +
+ ) } { isEmailUserAdmin( mailbox ) && ( { translate( 'Admin', { @@ -102,6 +126,9 @@ function EmailPlanMailboxesList( { ) } + { context === 'email' && ( + + ) } { ! mailbox.temporary && ! isGoogleConfiguring && ( ) } @@ -130,7 +157,6 @@ function EmailPlanMailboxesList( { switch ( context ) { case 'domains': case 'hosting-overview': - case 'email': return ( <> { ( isGoogleConfiguring || isAccountWarningPresent ) && ( @@ -155,10 +181,12 @@ function EmailPlanMailboxesList( { { accountType === EMAIL_ACCOUNT_TYPE_FORWARD && ( <> - + > + + { ! hasGSuiteWithUs( domain ) && ! hasTitanMailWithUs( domain ) && ( ) } @@ -182,6 +210,7 @@ function EmailPlanMailboxesList( { ); + case 'email': default: { if ( isGoogleConfiguring ) { return ; diff --git a/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-header.tsx b/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-header.tsx new file mode 100644 index 00000000000000..3237d9a794d425 --- /dev/null +++ b/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-header.tsx @@ -0,0 +1,34 @@ +import { CompactCard } from '@automattic/components'; +import { Button } from '@wordpress/components'; +import clsx from 'clsx'; +import { useTranslate } from 'i18n-calypso'; +import React from 'react'; + +type Props = React.PropsWithChildren< { + className?: string; + actionPath?: string; +} >; +export default function EmailForwardHeader( { className, children, actionPath }: Props ) { + const translate = useTranslate(); + + return ( +
+ +
+ { translate( 'Email forwards' ) } +
+
+ { translate( 'Destination' ) } +
+
+ { actionPath && ( + + ) } +
+
+ { children } +
+ ); +} diff --git a/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-secondary-details.tsx b/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-secondary-details.tsx new file mode 100644 index 00000000000000..0d3ca13a70d3c0 --- /dev/null +++ b/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-secondary-details.tsx @@ -0,0 +1,22 @@ +import { Gridicon } from '@automattic/components'; +import { getEmailForwardAddress, isEmailForward } from 'calypso/lib/emails'; +import type { Mailbox } from 'calypso/data/emails/types'; + +type Props = { + mailbox: Mailbox; + hideIcon?: boolean; +}; +function EmailForwardSecondaryDetails( { mailbox, hideIcon }: Props ) { + if ( isEmailForward( mailbox ) ) { + return ( +
+ { ! hideIcon && } + { getEmailForwardAddress( mailbox ) } +
+ ); + } + + return null; +} + +export default EmailForwardSecondaryDetails; diff --git a/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-target.tsx b/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-target.tsx deleted file mode 100644 index cb1c515dac2371..00000000000000 --- a/client/my-sites/email/email-management/home/email-plan-mailboxes/email-forward-target.tsx +++ /dev/null @@ -1,24 +0,0 @@ -const ForwardIcon = () => ( - - - -); - -type Props = { - target: string; - showIcon?: boolean; - title: string; -}; -function EmailForwardTarget( { target, showIcon, title }: Props ) { - return ( -
- { showIcon && } - { target } -
- ); -} - -export default EmailForwardTarget; diff --git a/client/my-sites/email/email-management/home/email-plan/index.jsx b/client/my-sites/email/email-management/home/email-plan/index.jsx index 86b4a9ccfb685b..ad64fa02fbeb59 100644 --- a/client/my-sites/email/email-management/home/email-plan/index.jsx +++ b/client/my-sites/email/email-management/home/email-plan/index.jsx @@ -93,6 +93,7 @@ function getEmailForwardLimit( data ) { function getMailboxes( data ) { const account = getAccount( data ); + return account?.emails ?? []; } diff --git a/client/my-sites/email/email-management/style.scss b/client/my-sites/email/email-management/style.scss index b4024225175985..c68242c128f6c0 100644 --- a/client/my-sites/email/email-management/style.scss +++ b/client/my-sites/email/email-management/style.scss @@ -1,10 +1,6 @@ @import "@wordpress/base-styles/breakpoints"; @import "@wordpress/base-styles/mixins"; -.email-management-main { - margin: 50px; -} - .email-management { .empty-content__illustration { width: 250px; @@ -18,12 +14,12 @@ */ .email-list-active { - >.card { + > .card { color: var(--color-text); display: flex; } - >.card.is-card-link:hover { + > .card.is-card-link:hover { background-color: var(--color-neutral-0); } } @@ -31,7 +27,7 @@ .email-list-active__item-icon { align-self: center; - >img, + > img, svg { display: block; height: 36px; @@ -45,7 +41,7 @@ } } - >.gridicon.gridicons-my-sites { + > .gridicon.gridicons-my-sites { fill: var(--color-wordpress-com); } } @@ -65,7 +61,7 @@ font-size: $font-body-small; margin-left: 14px; - >span { + > span { display: none; vertical-align: middle; @@ -82,7 +78,7 @@ } } - >svg { + > svg { height: 18px; margin-right: 6px; /* Push the icon down when there is no warning text */ @@ -104,21 +100,21 @@ } &.error { - >span { + > span { color: var(--color-error); } - >svg { + > svg { fill: var(--color-error); } } &.warning { - >span { + > span { color: var(--color-warning); } - >svg { + > svg { fill: var(--color-warning); } } @@ -132,7 +128,7 @@ align-items: center; display: flex; - >span { + > span { flex: 1; } @@ -163,13 +159,13 @@ } } -@mixin email_plan_header_status_color($color ) { +@mixin email_plan_header_status_color( $color ) { border-top: 5px solid $color; .email-plan-header__status { color: $color; - >svg { + > svg { fill: $color; } } @@ -179,7 +175,7 @@ display: flex; align-items: center; - >svg { + > svg { margin-right: 5px; } } @@ -195,32 +191,35 @@ } &.success { - @include email_plan_header_status_color(var(--color-success)); + @include email_plan_header_status_color( var( --color-success ) ); } &.warning { - @include email_plan_header_status_color(var(--color-warning)); + @include email_plan_header_status_color( var( --color-warning ) ); } &.error { - @include email_plan_header_status_color(var(--color-error)); + @include email_plan_header_status_color( var( --color-error ) ); } } .email-plan-header__icon { - - >img, + > img, svg { height: 36px; width: 36px; margin-right: 20px; } - >.gridicon.gridicons-my-sites { + > .gridicon.gridicons-my-sites { fill: var(--color-wordpress-com); } } +.email-plan-mailboxes-list__mailbox-list { + margin-top: 15px; +} + .email-mailbox-action-menu__main { position: absolute; right: 16px; @@ -230,7 +229,7 @@ right: 24px; } - >.button.is-borderless { + > .button.is-borderless { padding: 0; } } @@ -248,28 +247,28 @@ display: flex; line-height: 24px; - >img, - >svg { + > img, + > svg { margin-right: 8px; } &.is-selected, &:not(:disabled):hover, &:focus { - >img { + > img { filter: grayscale(100%) brightness(0.2) invert(1); } - >svg { + > svg { fill: var(--studio-white); } } - &:disabled>svg { + &:disabled > svg { fill: currentColor; } - &.external-link>.gridicons-external { + &.external-link > .gridicons-external { display: none; } } @@ -317,24 +316,38 @@ } } - &.is-placeholder>span, + &.is-placeholder > span, &.is-placeholder .email-plan-mailboxes-list__mailbox-list-item-main { - @include placeholder(--color-neutral-5); + @include placeholder( --color-neutral-5 ); width: 50%; } - &.no-emails>span { + &.no-emails > span { color: var(--color-text-subtle); font-style: italic; } + .email-plan-mailboxes-list__mailbox-list-link { + display: inline-flex; + gap: 10px; + } + + .email-plan-mailboxes-list__mailbox-list-item-main svg { + fill: var(--studio-gray-40); + margin: 0; + } + + .email-plan-mailboxes-list__mailbox-list-item-main span { + vertical-align: middle; + word-break: break-all; + } + .email-plan-mailboxes-list__mailbox-secondary-details { display: flex; gap: 10px; - flex-grow: unset; } - >.badge { + > .badge { align-self: center; /* Hide admin badge in mobile layouts */ display: none; @@ -344,6 +357,47 @@ margin-left: 10px; } } + + .email-mailbox-warnings__warning { + border-top: 1px solid var(--color-neutral-5); + color: var(--color-error); + font-size: $font-body-small; + line-height: 24px; + margin-top: 18px; + padding-top: 18px; + + @include break-xlarge { + border-top: none; + margin-left: 16px; + margin-right: 16px; + margin-top: 0; + padding-top: 0; + } + + > svg { + margin-right: 6px; + vertical-align: middle; + } + + > span { + vertical-align: middle; + } + } + + .email-mailbox-warnings__action { + margin-top: 12px; + + @include break-xlarge { + flex: 0.75; + margin-top: 0; + margin-left: auto; + text-align: right; + } + + > .button > .gridicons-external { + margin-left: 6px; + } + } } .email-plan-warnings__container { @@ -395,9 +449,7 @@ /** * Context: All Domain Management */ -.wpcom-site .hosting-dashboard-layout.sites-dashboard .hosting-dashboard-item-view__content .context-all-domain-management, -.is-section-email, -/* For Domain and Email related pages under site context. */ +.wpcom-site .hosting-dashboard-layout.sites-dashboard .hosting-dashboard-item-view__content .context-all-domain-management, /* For Domain and Email related pages under site context. */ .context-all-domain-management { &.main { margin: 0 auto; @@ -428,6 +480,7 @@ /* Mailboxes */ .email-plan-mailboxes-list { &__mailbox-list { + margin-top: 0; padding-bottom: 1.875rem; @include break-medium { @@ -443,7 +496,6 @@ .card.section-header { margin: 0 1.5rem; padding: 1.75rem 0 1.25em 0; - align-items: center; } .section-header__label-text { @@ -475,6 +527,10 @@ } } + &-item > div { + flex-grow: initial; + } + .email-mailbox-action-menu__main { right: 0; top: 18px; @@ -519,6 +575,8 @@ } .email-plan-warnings__message { + flex-grow: 1; + span { display: inline-block; max-width: 700px; @@ -545,7 +603,7 @@ } } - .email-plan-mailboxes-list__mailbox-list-item>.badge { + .email-plan-mailboxes-list__mailbox-list-item > .badge { flex: none; } @@ -554,6 +612,76 @@ color: var(--theme-highlight-color-50); opacity: 0.5; } + + .email-plan-mailboxes-list__email-forward { + .section-header__label, + .email-plan-mailboxes-list__mailbox-list-item-main { + flex: 1 1 0; + } + + .section-header__actions, + .email-mailbox-action-menu__main { + min-width: 80px; + flex: 0 0 auto; + } + + .email-mailbox-action-menu__main { + text-align: end; + + @include break-xlarge { + position: static; + } + } + + .section-header__label { + font-weight: 500; + + &.destination { + display: none; + + @include break-xlarge { + display: block; + } + } + } + + .email-plan-mailboxes-list__mailbox-list-item-main { + font-size: 0.875rem; + + span { + align-self: center; + } + } + + .email-plan-mailboxes-list__mailbox-secondary-details svg { + width: 1rem; + } + + .email-mailbox-warnings { + .email-mailbox-warnings__warning, + .email-mailbox-warnings__action { + display: inline-block; + border: none; + padding: 0; + margin: 0; + } + + .email-mailbox-warnings__action { + button { + padding: 0; + } + } + + .email-mailbox-warnings__warning { + margin-left: 0; + margin-right: 0.75rem; + + svg { + fill: var(--color-error); + } + } + } + } } .email-plan-mailboxes-list__email-upgrade-notice { @@ -586,4 +714,4 @@ flex: 1 1 auto; margin: 0 0.75rem; } -} \ No newline at end of file +} diff --git a/packages/components/src/confirmation-dialog/index.tsx b/packages/components/src/confirmation-dialog/index.tsx deleted file mode 100644 index d4a6186ab28d26..00000000000000 --- a/packages/components/src/confirmation-dialog/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { Button } from '@wordpress/components'; -import React, { useEffect } from 'react'; -import './style.scss'; - -// DELETE ME ONCE https://wordpress.github.io/gutenberg/?path=/docs/components-experimental-confirmdialog--docs is live. -// At this date, WP's Dialog is sadly broken. -interface ConfirmationDialogProps { - isOpen: boolean; - onConfirm: () => void; - onCancel: () => void; - confirmButtonText: string; - cancelButtonText: string; - children: React.ReactNode; -} - -export function ConfirmationDialog( { - isOpen, - onConfirm, - onCancel, - confirmButtonText, - cancelButtonText, - children, -}: ConfirmationDialogProps ) { - const dialogRef = React.useRef< HTMLDialogElement >( null ); - useEffect( () => { - const dialog = dialogRef.current; - if ( dialog ) { - if ( isOpen ) { - dialog.showModal(); - } else { - dialog.close(); - } - } - }, [ isOpen, dialogRef ] ); - return ( - -
{ children }
-
- - { /* eslint-disable-next-line jsx-a11y/no-autofocus */ } - -
-
- ); -} diff --git a/packages/components/src/confirmation-dialog/style.scss b/packages/components/src/confirmation-dialog/style.scss deleted file mode 100644 index 5237cb8bf8d212..00000000000000 --- a/packages/components/src/confirmation-dialog/style.scss +++ /dev/null @@ -1,22 +0,0 @@ -.automattic-components-dialog { - box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08), 0 15px 27px rgba(0, 0, 0, 0.07), 0 30px 36px rgba(0, 0, 0, 0.04), 0 50px 43px rgba(0, 0, 0, 0.02); - border: none; - border-radius: 4px; - text-align: start; - padding: 30px; - box-sizing: border-box; - // mobile - 10px margins - max-width: calc(100% - 20px); -} - -.automattic-components-dialog::backdrop { - background-color: rgba(0, 0, 0, 0.35); -} - -.automattic-components-dialog__actions { - display: flex; - justify-content: flex-end; - gap: 10px; - align-items: center; - margin-top: 20px; -} \ No newline at end of file diff --git a/packages/components/src/index.ts b/packages/components/src/index.ts index 1946282445155e..4c8e62278095f2 100644 --- a/packages/components/src/index.ts +++ b/packages/components/src/index.ts @@ -19,7 +19,6 @@ export { default as CircularProgressBar } from './circular-progress-bar'; export { default as ResponsiveToolbarGroup } from './responsive-toolbar-group'; export { default as Ribbon } from './ribbon'; export { default as RootChild } from './root-child'; -export { ConfirmationDialog as ConfirmationDialog } from './confirmation-dialog'; export { default as ScreenReaderText } from './screen-reader-text'; export { useScrollToTop } from './scroll-to-top/use-scroll-to-top'; export { default as SelectDropdown } from './select-dropdown';