Fixed validation and edge cases for managed email UI (#19121)

refs GRO-73

- fixed validation for reply-to address
- fixed rendering of default values for reply-to and sender-from fields
- added a temporary generic message for the verification confirmation,
so that it's compatible with both reply-to and from address changes. The
message will be improved in a follow-up commit (pending an API change).
This commit is contained in:
Sag 2023-11-23 19:30:28 -03:00 committed by GitHub
parent 8a5e9fb9f6
commit 819ddccc72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 121 additions and 94 deletions

View File

@ -52,7 +52,7 @@ const Newsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
NiceModal.show(ConfirmationModal, { NiceModal.show(ConfirmationModal, {
title: 'Email address verified', title: 'Email address verified',
prompt: <>Success! From address for newsletter <NavigateToNewsletter id={updatedNewsletter.id}>{updatedNewsletter.name}</NavigateToNewsletter> changed to <strong>{updatedNewsletter.sender_email}</strong></>, prompt: <>Success! Email address for newsletter <NavigateToNewsletter id={updatedNewsletter.id}>{updatedNewsletter.name}</NavigateToNewsletter> has been changed.</>,
okLabel: 'Close', okLabel: 'Close',
cancelLabel: '', cancelLabel: '',
onOk: confirmModal => confirmModal?.remove() onOk: confirmModal => confirmModal?.remove()

View File

@ -15,12 +15,19 @@ import {getSettingValues} from '@tryghost/admin-x-framework/api/settings';
import {textColorForBackgroundColor} from '@tryghost/color-utils'; import {textColorForBackgroundColor} from '@tryghost/color-utils';
import {useGlobalData} from '../../../providers/GlobalDataProvider'; import {useGlobalData} from '../../../providers/GlobalDataProvider';
const renderFrom = (newsletter: Newsletter, config: Config, defaultEmailAddress: string|undefined) => { const renderSenderEmail = (newsletter: Newsletter, config: Config, defaultEmailAddress: string|undefined) => {
if (isManagedEmail(config) && defaultEmailAddress) { if (isManagedEmail(config) && !hasSendingDomain(config) && defaultEmailAddress) {
if (!hasSendingDomain(config)) {
// Not changeable: sender_email is ignored // Not changeable: sender_email is ignored
return defaultEmailAddress; return defaultEmailAddress;
} }
if (isManagedEmail(config) && hasSendingDomain(config)) {
// Only return sender_email if the domain names match
if (newsletter.sender_email?.split('@')[1] === sendingDomain(config)) {
return newsletter.sender_email;
} else {
return '';
}
} }
return newsletter.sender_email || defaultEmailAddress || ''; return newsletter.sender_email || defaultEmailAddress || '';
@ -32,12 +39,23 @@ const renderReplyToEmail = (newsletter: Newsletter, config: Config, supportEmail
} }
if (newsletter.sender_reply_to === 'newsletter') { if (newsletter.sender_reply_to === 'newsletter') {
return renderFrom(newsletter, config, defaultEmailAddress); return renderSenderEmail(newsletter, config, defaultEmailAddress);
} else if (newsletter.sender_reply_to === 'support') {
return supportEmailAddress || defaultEmailAddress || '';
} else {
return newsletter.sender_reply_to;
} }
if (newsletter.sender_reply_to === 'support') {
return supportEmailAddress || defaultEmailAddress || '';
}
if (isManagedEmail(config) && hasSendingDomain(config)) {
// Only return sender_reply_to if the domain names match
if (newsletter.sender_reply_to.split('@')[1] === sendingDomain(config)) {
return newsletter.sender_reply_to;
} else {
return '';
}
}
return newsletter.sender_reply_to;
}; };
const Sidebar: React.FC<{ const Sidebar: React.FC<{
@ -59,7 +77,7 @@ const Sidebar: React.FC<{
const [siteTitle] = getSettingValues(localSettings, ['title']) as string[]; const [siteTitle] = getSettingValues(localSettings, ['title']) as string[];
const handleError = useHandleError(); const handleError = useHandleError();
let newsletterAddress = renderFrom(newsletter, config, defaultEmailAddress); let newsletterAddress = renderSenderEmail(newsletter, config, defaultEmailAddress);
const replyToEmails = [ const replyToEmails = [
{label: `Newsletter address (${newsletterAddress})`, value: 'newsletter'}, {label: `Newsletter address (${newsletterAddress})`, value: 'newsletter'},
@ -137,9 +155,26 @@ const Sidebar: React.FC<{
}; };
const renderSenderEmailField = () => { const renderSenderEmailField = () => {
if (isManagedEmail(config)) { // Self-hosters, or legacy Pro users
if (!isManagedEmail(config)) {
return (
<TextField
error={Boolean(errors.sender_email)}
hint={errors.sender_email}
placeholder={newsletterAddress || ''}
title="Sender email address"
value={newsletter.sender_email || ''}
onBlur={validate}
onChange={e => updateNewsletter({sender_email: e.target.value})}
onKeyDown={() => clearError('sender_email')}
/>
);
}
// Pro users with custom sending domains
if (hasSendingDomain(config)) { if (hasSendingDomain(config)) {
const sendingEmailUsername = newsletter.sender_email?.split('@')[0]; const sendingEmail = renderSenderEmail(newsletter, config, defaultEmailAddress);
const sendingEmailUsername = sendingEmail?.split('@')[0];
return ( return (
<TextField <TextField
@ -157,7 +192,9 @@ const Sidebar: React.FC<{
onKeyDown={() => clearError('sender_email')} onKeyDown={() => clearError('sender_email')}
/> />
); );
} else { }
// Pro users without custom sending domains
return ( return (
<SettingGroupContent <SettingGroupContent
values={[ values={[
@ -170,59 +207,11 @@ const Sidebar: React.FC<{
]} ]}
/> />
); );
}
}
return (
<TextField
error={Boolean(errors.sender_email)}
hint={errors.sender_email}
placeholder={newsletterAddress}
title="Sender email address"
value={newsletter.sender_email || ''}
onBlur={validate}
onChange={e => updateNewsletter({sender_email: e.target.value})}
onKeyDown={() => clearError('sender_email')}
/>
);
}; };
const renderReplyToEmailField = () => { const renderReplyToEmailField = () => {
if (isManagedEmail(config)) { // Self-hosters, or legacy Pro users
if (hasSendingDomain(config)) { if (!isManagedEmail(config)) {
const replyToEmailUsername = ['newsletter', 'support'].includes(newsletter.sender_reply_to) ? '' : newsletter.sender_reply_to?.split('@')[0];
return (
<TextField
error={Boolean(errors.sender_reply_to)}
hint={errors.sender_reply_to}
rightPlaceholder={`@${sendingDomain(config)}`}
title="Reply-to address"
value={replyToEmailUsername || ''}
onBlur={validate}
onChange={(e) => {
const username = e.target.value?.split('@')[0];
const newEmail = username ? `${username}@${sendingDomain(config)}` : '';
updateNewsletter({sender_reply_to: newEmail});
}}
onKeyDown={() => clearError('sender_reply_to')}
/>
);
} else {
return (
<TextField
error={Boolean(errors.sender_reply_to)}
hint={errors.sender_reply_to}
placeholder={newsletterAddress}
title="Reply-to email"
value={renderReplyToEmail(newsletter, config, supportEmailAddress, defaultEmailAddress)}
onBlur={validate}
onChange={e => updateNewsletter({sender_reply_to: e.target.value})}
onKeyDown={() => clearError('sender_reply_to')}
/>
);
}
}
return ( return (
<Select <Select
options={replyToEmails} options={replyToEmails}
@ -231,6 +220,44 @@ const Sidebar: React.FC<{
onSelect={option => updateNewsletter({sender_reply_to: option?.value})} onSelect={option => updateNewsletter({sender_reply_to: option?.value})}
/> />
); );
}
// Pro users with custom sending domains
if (hasSendingDomain(config)) {
const replyToEmail = ['newsletter', 'support'].includes(newsletter.sender_reply_to) ? '' : renderReplyToEmail(newsletter, config, supportEmailAddress, defaultEmailAddress);
const replyToEmailUsername = replyToEmail?.split('@')[0] || '';
return (
<TextField
error={Boolean(errors.sender_reply_to)}
hint={errors.sender_reply_to}
rightPlaceholder={`@${sendingDomain(config)}`}
title="Reply-to address"
value={replyToEmailUsername}
onBlur={validate}
onChange={(e) => {
const username = e.target.value?.split('@')[0];
const newEmail = username ? `${username}@${sendingDomain(config)}` : 'newsletter';
updateNewsletter({sender_reply_to: newEmail});
}}
onKeyDown={() => clearError('sender_reply_to')}
/>
);
}
// Pro users without custom sending domains
return (
<TextField
error={Boolean(errors.sender_reply_to)}
hint={errors.sender_reply_to}
placeholder={newsletterAddress || ''}
title="Reply-to email"
value={renderReplyToEmail(newsletter, config, supportEmailAddress, defaultEmailAddress) || ''}
onBlur={validate}
onChange={e => updateNewsletter({sender_reply_to: e.target.value})}
onKeyDown={() => clearError('sender_reply_to')}
/>
);
}; };
const tabs: Tab[] = [ const tabs: Tab[] = [
@ -535,7 +562,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
const {newsletters, meta} = await editNewsletter(formState); const {newsletters, meta} = await editNewsletter(formState);
if (meta?.sent_email_verification) { if (meta?.sent_email_verification) {
if (meta?.sent_email_verification[0] === 'sender_email') { if (meta?.sent_email_verification[0] === 'sender_email') {
const previousFrom = renderFrom(newsletters[0], config, defaultEmailAddress); const previousFrom = renderSenderEmail(newsletters[0], config, defaultEmailAddress);
NiceModal.show(ConfirmationModal, { NiceModal.show(ConfirmationModal, {
title: 'Confirm newsletter email address', title: 'Confirm newsletter email address',
@ -584,7 +611,7 @@ const NewsletterDetailModalContent: React.FC<{newsletter: Newsletter; onlyOne: b
newErrors.sender_email = 'Invalid email.'; newErrors.sender_email = 'Invalid email.';
} }
if (isManagedEmail(config) && formState.sender_reply_to && (!validator.isEmail(formState.sender_reply_to))) { if (formState.sender_reply_to && !validator.isEmail(formState.sender_reply_to) && !['newsletter', 'support'].includes(formState.sender_reply_to)) {
newErrors.sender_reply_to = 'Invalid email.'; newErrors.sender_reply_to = 'Invalid email.';
} }