Implement the Manage Sender Domain component and

The AuthorizeSenderDomainModal

MAILPOET-4302
This commit is contained in:
Oluwaseun Olorunsola
2022-07-21 14:53:35 +01:00
committed by Veljko V
parent 5c3a9fe9b2
commit 847d199352
7 changed files with 322 additions and 6 deletions

View File

@ -0,0 +1,187 @@
import { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { noop } from 'lodash';
import { MailPoet } from 'mailpoet';
import { Modal } from 'common/modal/modal';
import {
ManageSenderDomain,
SenderDomainDnsItem,
SenderDomainEntity,
} from 'common/manage_sender_domain';
import { isErrorResponse, Response, ErrorResponse } from 'ajax';
interface SenderDomainApiResponseType extends Response {
data: SenderDomainDnsItem[];
}
type VerifyResponseType = {
dns: SenderDomainDnsItem[];
ok: boolean;
error?: string;
};
interface SenderDomainApiVerifyResponseType extends Response {
data: VerifyResponseType;
}
type ApiActionType = 'fetch' | 'create' | 'verify';
/**
* @param {string} domain - Sender Domain
* @param {ApiActionType} type - action type
* @returns {Promise}
*/
const makeApiRequest = (domain: string, type: ApiActionType = 'fetch') => {
let requestAction = 'getAuthorizedSenderDomains';
if (type === 'create') {
requestAction = 'createAuthorizedSenderDomain';
} else if (type === 'verify') {
requestAction = 'verifyAuthorizedSenderDomain';
}
return MailPoet.Ajax.post({
api_version: MailPoet.apiVersion,
endpoint: 'settings',
action: requestAction,
data: { domain },
});
};
const getApiErrorMessage = (error: { error?: ErrorResponse }): string =>
isErrorResponse(error) && error.errors[0] && error.errors[0].message
? error.errors[0].message
: '';
const generateRowData = (senderDomain: string, dns: SenderDomainDnsItem[]) => {
const row: SenderDomainEntity[] = [
{
domain: senderDomain,
dns,
},
];
return row;
};
type Props = {
senderDomain: string;
onRequestClose: () => void;
setVerifiedSenderDomain?: (senderDomain: string) => void;
};
function AuthorizeSenderDomainModal({
senderDomain,
onRequestClose,
setVerifiedSenderDomain,
}: Props): JSX.Element {
const [errorMessage, setErrorMessage] = useState('');
const [loadingButton, setLoadingButton] = useState(false);
const [rowData, setRowData] = useState<SenderDomainEntity[]>([]);
const modalIsOpened = useRef<boolean>(false);
const performStateUpdate = (callback: (param) => void, args) => {
if (!modalIsOpened.current) return; // do nothing if modal is not opened
callback(args);
};
const verifyDnsButtonClicked = async () => {
setLoadingButton(true);
try {
const res: SenderDomainApiVerifyResponseType = await makeApiRequest(
senderDomain,
'verify',
);
if (!modalIsOpened.current) return;
setRowData(generateRowData(senderDomain, res.data.dns));
if (res.data.ok) {
// record verified, close the modal
setErrorMessage('');
setVerifiedSenderDomain(senderDomain);
onRequestClose();
}
} catch (e) {
const error: { error?: ErrorResponse; meta?: VerifyResponseType } = e;
if (!modalIsOpened.current) return;
setRowData(generateRowData(senderDomain, error?.meta?.dns || []));
const apiErrorMessage = getApiErrorMessage(e);
setErrorMessage(apiErrorMessage || error?.meta?.error || '');
}
performStateUpdate(setLoadingButton, false);
};
useEffect(() => {
if (!senderDomain) {
return null;
}
modalIsOpened.current = true;
const allSenderDomains = window.mailpoet_all_sender_domains || [];
(async () => {
try {
if (allSenderDomains.includes(senderDomain)) {
// sender domain already exist
const res: SenderDomainApiResponseType = await makeApiRequest(
senderDomain,
);
performStateUpdate(
setRowData,
generateRowData(senderDomain, res.data),
);
} else {
// create new sender domain
const res: SenderDomainApiResponseType = await makeApiRequest(
senderDomain,
'create',
);
performStateUpdate(
setRowData,
generateRowData(senderDomain, res.data),
);
}
} catch (e) {
const apiErrorMessage = getApiErrorMessage(e);
performStateUpdate(setErrorMessage, apiErrorMessage);
}
})().catch(() => {
// do nothing
});
return () => {
modalIsOpened.current = false;
};
}, [senderDomain]);
return (
<Modal
onRequestClose={onRequestClose}
contentClassName="authorize-sender-domain-modal"
>
{errorMessage && (
<strong className="mailpoet_error_item mailpoet_error">
{' '}
{errorMessage}{' '}
</strong>
)}
<ManageSenderDomain
rows={rowData}
verifyDnsButtonClicked={verifyDnsButtonClicked}
loadingButton={loadingButton}
/>
</Modal>
);
}
AuthorizeSenderDomainModal.propTypes = {
senderDomain: PropTypes.string.isRequired,
};
AuthorizeSenderDomainModal.defaultProps = {
setVerifiedSenderDomain: noop,
};
export { AuthorizeSenderDomainModal };

View File

@ -0,0 +1,2 @@
export * from './manage_sender_domain_types';
export * from './manage_sender_domain';

View File

@ -0,0 +1,100 @@
import PropTypes from 'prop-types';
import { noop } from 'lodash';
import ReactStringReplace from 'react-string-replace';
import { Button, Loader, TypographyHeading as Heading } from 'common';
import { MailPoet } from 'mailpoet';
import { SenderDomainEntity } from './manage_sender_domain_types';
type Props = {
max_width: string;
rows: Array<SenderDomainEntity>;
loadingButton: boolean;
verifyDnsButtonClicked: () => void;
};
function ManageSenderDomain({
max_width,
rows,
loadingButton,
verifyDnsButtonClicked,
}: Props) {
if (rows.length === 0) return <Loader size={84} />;
const { dns, domain } = rows[0];
return (
<div>
<Heading level={2}>
{' '}
{MailPoet.I18n.t('manageSenderDomainHeaderTitle')}{' '}
</Heading>
<p>
{ReactStringReplace(
MailPoet.I18n.t('manageSenderDomainHeaderSubtitle'),
/\[link](.*?)\[\/link]/g,
(match) => (
<a
key={match}
className="mailpoet-link"
href="https://kb.mailpoet.com/article/188-how-to-set-up-mailpoet-sending-service#dns"
target="_blank"
rel="noopener noreferrer"
>
{match}
</a>
),
)}
</p>
<table className="widefat fixed" style={{ maxWidth: max_width }}>
<thead>
<tr>
<th> {MailPoet.I18n.t('manageSenderDomainTableHeaderType')} </th>
<th> {MailPoet.I18n.t('manageSenderDomainTableHeaderHost')} </th>
<th> {MailPoet.I18n.t('manageSenderDomainTableHeaderValue')} </th>
<th> {MailPoet.I18n.t('manageSenderDomainTableHeaderStatus')} </th>
</tr>
</thead>
<tbody>
{dns.map((dnsRecord) => (
<tr key={`row_${domain}_${dnsRecord.host}`}>
<td>{dnsRecord.type}</td>
<td>{dnsRecord.host}</td>
<td>{dnsRecord.value}</td>
<td>{dnsRecord.status}</td>
</tr>
))}
</tbody>
</table>
<Button withSpinner={loadingButton} onClick={verifyDnsButtonClicked}>
{' '}
{MailPoet.I18n.t('manageSenderDomainVerifyButton')}{' '}
</Button>
</div>
);
}
ManageSenderDomain.propTypes = {
max_width: PropTypes.string,
rows: PropTypes.arrayOf(
PropTypes.shape({
domain: PropTypes.string.isRequired,
dns: PropTypes.arrayOf(
PropTypes.shape({
host: PropTypes.string,
value: PropTypes.string,
type: PropTypes.string,
status: PropTypes.string,
message: PropTypes.string,
}),
).isRequired,
}),
).isRequired,
verifyDnsButtonClicked: PropTypes.func,
};
ManageSenderDomain.defaultProps = {
max_width: 'auto',
verifyDnsButtonClicked: noop,
};
export { ManageSenderDomain };

View File

@ -0,0 +1,20 @@
type SenderDomainVerificationStatus = 'valid' | 'invalid' | 'pending';
type SenderDomainDnsItem = {
host: string;
value: string;
type: string;
status: SenderDomainVerificationStatus;
message: string;
};
type SenderDomainEntity = {
domain: string;
dns: Array<SenderDomainDnsItem>;
};
export {
SenderDomainVerificationStatus,
SenderDomainDnsItem,
SenderDomainEntity,
};

View File

@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { MailPoet } from 'mailpoet';
import ReactStringReplace from 'react-string-replace';
import { AuthorizeSenderEmailModal } from 'common/authorize_sender_email_modal';
import { AuthorizeSenderDomainModal } from 'common/authorize_sender_domain_modal';
const userHostDomain = window.location.hostname.replace('www.', '');
const suggestedEmailAddress = `contact@${userHostDomain}`;
@ -63,13 +64,11 @@ function SenderEmailAddressWarning({
return (
<>
{showAuthorizedEmailModal && (
// TODO: Change me. This should open the sender domain modal
<AuthorizeSenderEmailModal
senderEmail={emailAddress}
<AuthorizeSenderDomainModal
senderDomain={emailAddressDomain}
onRequestClose={() => {
setShowAuthorizedEmailModal(false);
}}
setAuthorizedAddress={setAuthorizedEmailAddress}
/>
)}
<p className="sender_email_address_warning">
@ -80,7 +79,7 @@ function SenderEmailAddressWarning({
<a
key={match}
className="mailpoet-link"
href="https://kb.mailpoet.com/article/328-set-up-dkim-for-your-sender-domain"
href="https://kb.mailpoet.com/article/295-spf-and-dkim"
target="_blank"
rel="noopener noreferrer"
onClick={loadModal}

View File

@ -341,7 +341,7 @@ class Settings extends APIEndpoint {
if (!$response['ok']) {
// sender domain verification error. probably an improper setup
return $this->badRequest([
APIError::BAD_REQUEST => WPFunctions::get()->__('failed sender domain verification', 'mailpoet'),
APIError::BAD_REQUEST => WPFunctions::get()->__($response['error'] ?? 'failed sender domain verification', 'mailpoet'),
], $response);
}

View File

@ -116,6 +116,14 @@ jQuery('#adminmenu #toplevel_page_mailpoet-newsletters')
'setFromAddressEmailNotAuthorized': __('Cant use this email yet! [link]Please authorize it first[/link].', 'mailpoet'),
'setFromAddressEmailUnknownError': __('An error occured when saving FROM email address.', 'mailpoet'),
'manageSenderDomainHeaderTitle': __('Manage Sender Domain ', 'mailpoet'),
'manageSenderDomainHeaderSubtitle': __('To help your audience and MailPoet authenticate you as the domain owner, please add the following DNS records to your domains DNS and click “Verify the DNS records”. Please note that it may take up to 24 hours for DNS changes to propagate after you make the change. [link]Read the guide[/link].', 'mailpoet'),
'manageSenderDomainTableHeaderType': __('Type', 'mailpoet'),
'manageSenderDomainTableHeaderHost': __('Host', 'mailpoet'),
'manageSenderDomainTableHeaderValue': __('Value', 'mailpoet'),
'manageSenderDomainTableHeaderStatus': __('Status', 'mailpoet'),
'manageSenderDomainVerifyButton': __('Verify the DNS records', 'mailpoet'),
'reviewRequestHeading': _x('Thank you! Time to tell the world?', 'After a user gives us positive feedback via the NPS poll, we ask them to review our plugin on WordPress.org.'),
'reviewRequestDidYouKnow': __('[username], did you know that hundreds of WordPress users read the reviews on the plugin repository? Theyre also a source of inspiration for our team.'),
'reviewRequestUsingForDays': _n('Youve been using MailPoet for [days] day now, and we would love to read your own review.', 'Youve been using MailPoet for [days] days now, and we would love to read your own review.', installed_days_ago),