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

Domains: Implement multi-target email forwards #98837

Open
wants to merge 43 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
7c135cb
Implement multi email forwards
alshakero Jan 23, 2025
3e50f1b
Add most of the needed UI
alshakero Jan 24, 2025
6c0b00d
Switch to `new`
alshakero Jan 25, 2025
81da338
Fix email section in Upgrades > Email section
alshakero Jan 27, 2025
d0896ba
CSS
alshakero Jan 27, 2025
7f4a623
Handle duplicates
alshakero Jan 27, 2025
3537d35
Limit globally
alshakero Jan 28, 2025
24b1c4a
Fix duplication
alshakero Jan 28, 2025
07b7cb1
Fix optimistic UI mutation
alshakero Jan 28, 2025
86a706c
Fix destination encoding
alshakero Jan 28, 2025
b1738ab
Merge branch 'trunk' into implement/mutliple-email-forwards
alshakero Jan 28, 2025
6f307e2
Bring back TS comment
alshakero Jan 28, 2025
7c80149
Address feedback
alshakero Jan 28, 2025
9dc7496
Code comment
alshakero Jan 28, 2025
f97f1a8
Merge branch 'trunk' into implement/mutliple-email-forwards
alshakero Jan 28, 2025
b32f5b6
Fix email context
alshakero Jan 28, 2025
9c5db5c
Unify design amongst all contexts
alshakero Jan 28, 2025
fe82dbf
More unification
alshakero Jan 28, 2025
ac8086a
Implement new input form
alshakero Jan 29, 2025
808bd2c
Add padding top
alshakero Jan 29, 2025
bc22633
Implement forward list
alshakero Jan 30, 2025
cf4ce9b
Visual fixes
alshakero Jan 30, 2025
6e6d407
Merge branch 'trunk' into implement/mutliple-email-forwards
alshakero Jan 30, 2025
1937fb7
Don't allow @
alshakero Jan 30, 2025
77afbbe
UX improvements
alshakero Jan 30, 2025
17b6a29
Handle duplicates
alshakero Jan 30, 2025
881c7d2
Refactor
alshakero Jan 30, 2025
5a22366
Remove unneeded file
alshakero Jan 30, 2025
0492535
Fix typo
alshakero Jan 30, 2025
b9f56ec
Improve code comment
alshakero Jan 30, 2025
129e2b0
Remove unused code
alshakero Jan 30, 2025
da3b5f9
Remove more unused code
alshakero Jan 30, 2025
f35e394
Revert "Remove unneeded file"
alshakero Jan 30, 2025
061fb2f
Refactor
alshakero Jan 30, 2025
70ff428
Fix validation and stop when tired next time
alshakero Jan 31, 2025
a4001c4
Bring back context menus
alshakero Jan 31, 2025
562e9b5
Bring back context meu
alshakero Jan 31, 2025
e182922
Remove `resend` for active addresses
alshakero Jan 31, 2025
cb1934f
Add happy state
alshakero Jan 31, 2025
94631b6
Fix focus issue WP dialog
alshakero Jan 31, 2025
9e60359
Switch to WP dropdown for better acccessibility
alshakero Jan 31, 2025
ad48071
Don't show resend when there are no warnings
alshakero Jan 31, 2025
33bd2b2
Remove unused code
alshakero Feb 1, 2025
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
13 changes: 13 additions & 0 deletions client/data/emails/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ export type EmailAccountEmail = {
warnings: Warning[];
};

export type AlterDestinationParams = {
mailbox: string;
destination: string;
domain: string;
};

type EmailAccountDomain = {
domain: string;
is_primary: boolean;
Expand Down Expand Up @@ -45,4 +51,11 @@ export type Mailbox = {
mailbox: string;
warnings?: Warning[];
temporary?: boolean;
target: string;
};

export type MailboxesCluster = {
mailboxes: Mailbox[];
mailbox: string;
domain: string;
};
16 changes: 9 additions & 7 deletions client/data/emails/use-add-email-forward-mutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import { getSelectedSiteId } from 'calypso/state/ui/selectors';
import { getCacheKey as getEmailAccountsQueryKey } from './use-get-email-accounts-query';
import type { UseMutationOptions } from '@tanstack/react-query';

const ArrayOfFive = new Array( 5 );

type AddMailboxFormData = {
destination: string;
destinations: typeof ArrayOfFive;
mailbox: string;
};

Expand Down Expand Up @@ -65,7 +67,7 @@ export default function useAddEmailForwardMutation(
};

mutationOptions.onMutate = async ( variables ) => {
const { mailbox, destination } = variables;
const { mailbox, destinations } = variables;
suppliedOnMutate?.( variables );

await queryClient.cancelQueries( { queryKey: emailAccountsQueryKey } );
Expand All @@ -79,16 +81,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: destination,
target: d,
temporary: true,
warnings: [],
},
} ) ),
],
[ 'mailbox' ],
[ 'asc' ]
Expand Down Expand Up @@ -179,10 +181,10 @@ export default function useAddEmailForwardMutation(
};

return useMutation< any, unknown, AddMailboxFormData, Context >( {
mutationFn: ( { mailbox, destination } ) =>
mutationFn: ( { mailbox, destinations } ) =>
wp.req.post( `/domains/${ encodeURIComponent( domainName ) }/email/new`, {
mailbox,
destination,
destinations,
} ),
...mutationOptions,
} );
Expand Down
21 changes: 13 additions & 8 deletions client/data/emails/use-remove-email-forward-mutation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 } from './types';
import type { EmailAccountEmail, AlterDestinationParams, Mailbox } from './types';
import type { UseMutationOptions } from '@tanstack/react-query';

type Context = {
Expand All @@ -26,7 +26,7 @@ const MUTATION_KEY = 'removeEmailForward';
export default function useRemoveEmailForwardMutation(
domainName: string,
mutationOptions: Omit<
UseMutationOptions< any, unknown, EmailAccountEmail, Context >,
UseMutationOptions< any, unknown, AlterDestinationParams, Context >,
'mutationFn'
> = {}
) {
Expand Down Expand Up @@ -70,7 +70,11 @@ export default function useRemoveEmailForwardMutation(
{
...previousEmailAccountsQueryData.accounts[ 0 ],
emails: emailForwards.filter(
( forward: EmailAccountEmail ) => forward.mailbox !== emailForward.mailbox
( forward: EmailAccountEmail ) =>
! (
forward.mailbox === emailForward.mailbox &&
forward.target === emailForward.destination
)
),
},
],
Expand Down Expand Up @@ -140,13 +144,14 @@ export default function useRemoveEmailForwardMutation(
dispatch( errorMessage );
};

return useMutation< any, unknown, EmailAccountEmail, Context >( {
mutationFn: ( { mailbox } ) =>
wp.req.post(
return useMutation< Mailbox, unknown, AlterDestinationParams, Context >( {
mutationFn: ( { mailbox, destination } ) => {
return wp.req.post(
`/domains/${ encodeURIComponent( domainName ) }/email/${ encodeURIComponent(
mailbox
) }/delete`
),
) }/${ encodeURIComponent( destination ) }/delete`
);
},
...mutationOptions,
} );
}
16 changes: 9 additions & 7 deletions client/data/emails/use-resend-verify-email-forward-mutation.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
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 { EmailAccountEmail } from './types';
import type { AlterDestinationParams } from './types';
import type { UseMutationOptions } from '@tanstack/react-query';

const MUTATION_KEY = 'reverifyEmailForward';
Expand All @@ -18,7 +17,10 @@ const MUTATION_KEY = 'reverifyEmailForward';
*/
export default function useResendVerifyEmailForwardMutation(
domainName: string,
mutationOptions: Omit< UseMutationOptions< any, unknown, EmailAccountEmail >, 'mutationFn' > = {}
mutationOptions: Omit<
UseMutationOptions< any, unknown, AlterDestinationParams >,
'mutationFn'
> = {}
) {
const dispatch = useDispatch();
const translate = useTranslate();
Expand All @@ -31,7 +33,7 @@ export default function useResendVerifyEmailForwardMutation(
mutationOptions.onSuccess = ( data, emailForward, context ) => {
suppliedOnSuccess?.( data, emailForward, context );

const destination = getEmailForwardAddress( emailForward );
const { destination } = emailForward;

const successMessage = translate(
'Successfully sent confirmation email for %(email)s to %(destination)s.',
Expand Down Expand Up @@ -69,12 +71,12 @@ export default function useResendVerifyEmailForwardMutation(
dispatch( errorNotice( failureMessage ) );
};

return useMutation< any, unknown, EmailAccountEmail >( {
mutationFn: ( { mailbox } ) =>
return useMutation< any, unknown, AlterDestinationParams >( {
mutationFn: ( { mailbox, destination } ) =>
wp.req.post(
`/domains/${ encodeURIComponent( domainName ) }/email/${ encodeURIComponent(
mailbox
) }/resend-verification`
) }/${ encodeURIComponent( destination ) }/resend-verification`
),
...mutationOptions,
} );
Expand Down

This file was deleted.

14 changes: 14 additions & 0 deletions client/lib/domains/email-forwarding/has-too-many-email-forwards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const MAX_FORWARD_DESTINATIONS = 5;

/**
* @param newEmailForward a string representing a new email forward
* @returns { boolean } If the email forward is has more than the maximum number of destinations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably need to rephrase this

*/
export function hasTooManyEmailForwardsForMailbox( newEmailForward, existingEmailForwards ) {
return (
existingEmailForwards?.filter(
( forward ) =>
forward.mailbox.localeCompare( newEmailForward, undefined, { sensitivity: 'base' } ) === 0
).length >= MAX_FORWARD_DESTINATIONS
);
}
20 changes: 13 additions & 7 deletions client/lib/domains/email-forwarding/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import emailValidator from 'email-validator';
import { mapValues } from 'lodash';
import { hasDuplicatedEmailForwards } from 'calypso/lib/domains/email-forwarding/has-duplicated-email-forwards';
import { hasTooManyEmailForwardsForMailbox } from 'calypso/lib/domains/email-forwarding/has-too-many-email-forwards';

function validateAllFields( fieldValues, existingEmailForwards = [] ) {
export function validateAllFields( fieldValues, existingEmailForwards = [] ) {
return mapValues( fieldValues, ( value, fieldName ) => {
const isValid = validateField( {
value,
Expand All @@ -17,16 +17,23 @@ function validateAllFields( fieldValues, existingEmailForwards = [] ) {
return [];
}

return hasDuplicatedEmailForwards( value, existingEmailForwards ) ? [ 'Duplicated' ] : [];
return hasTooManyEmailForwardsForMailbox( value, existingEmailForwards ) ? [ 'Exhausted' ] : [];
} );
}

function validateField( { name, value } ) {
export 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 );
case 'destinations': {
const nonEmpty = value.filter( ( v ) => v.trim() );
const hasDuplicates = value.length !== new Set( value ).size;
return (
! hasDuplicates &&
nonEmpty.length &&
nonEmpty.every( ( v ) => emailValidator.validate( v ) )
);
}
default:
return true;
}
Expand All @@ -35,4 +42,3 @@ function validateField( { name, value } ) {
export { getEmailForwardsCount } from './get-email-forwards-count';
export { hasEmailForwards } from './has-email-forwards';
export { getDomainsWithEmailForwards } from './get-domains-with-email-forwards';
export { validateAllFields };
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ type Props = {
showFormHeader?: boolean;
};

type OnAddEmailForwardProps =
| { index: number; name: 'mailbox'; value: string }
| { index: number; name: 'destinations'; value: string[] };

const EmailForwardingAddNewCompactList = ( {
onAddedEmailForwards,
onBeforeAddEmailForwards,
Expand All @@ -24,9 +28,9 @@ const EmailForwardingAddNewCompactList = ( {
}: Props ) => {
const translate = useTranslate();

const [ emailForwards, setEmailForwards ] = useState( [
{ destination: '', mailbox: '', isValid: false },
] );
const [ emailForwards, setEmailForwards ] = useState<
Array< { destinations: string[]; mailbox: string; isValid: boolean } >
>( [ { destinations: [], mailbox: '', isValid: false } ] );

const selectedSiteId = useSelector( getSelectedSiteId );

Expand All @@ -52,16 +56,16 @@ const EmailForwardingAddNewCompactList = ( {

onBeforeAddEmailForwards?.();

emailForwards?.map( ( { mailbox, destination } ) => {
addEmailForward( { mailbox, destination } );
emailForwards?.map( ( { mailbox, destinations } ) => {
addEmailForward( { mailbox, destinations } );
} );

onAddedEmailForwards?.();
};

const onAddNewEmailForward = () => {
setEmailForwards( ( prev ) => {
return [ ...prev, { destination: '', mailbox: '', isValid: false } ];
return [ ...prev, { destinations: [], mailbox: '', isValid: false } ];
} );
};

Expand All @@ -71,18 +75,17 @@ const EmailForwardingAddNewCompactList = ( {
setEmailForwards( newEmailForwards );
};

const onUpdateEmailForward = (
index: number,
name: 'destination' | 'mailbox',
value: string
) => {
const onUpdateEmailForward = ( { index, name, value }: OnAddEmailForwardProps ) => {
const newEmailForwards = [ ...emailForwards ];
newEmailForwards[ index ][ name ] = value;
if ( name === 'destinations' ) {
newEmailForwards[ index ].destinations = value;
} else {
newEmailForwards[ index ].mailbox = value;
}

const validEmailForward = validateAllFields( newEmailForwards[ index ], existingEmailForwards );
newEmailForwards[ index ].isValid =
validEmailForward.mailbox.length === 0 && validEmailForward.destination.length === 0;

validEmailForward.mailbox.length === 0 && validEmailForward.destinations.length === 0;
setEmailForwards( newEmailForwards );
};

Expand Down
Loading
Loading