diff --git a/mailpoet/assets/js/src/common/authorize_sender_domain_modal.tsx b/mailpoet/assets/js/src/common/authorize_sender_domain_modal.tsx new file mode 100644 index 0000000000..d5da9357fd --- /dev/null +++ b/mailpoet/assets/js/src/common/authorize_sender_domain_modal.tsx @@ -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([]); + const modalIsOpened = useRef(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 ( + + {errorMessage && ( + + {' '} + {errorMessage}{' '} + + )} + + + ); +} + +AuthorizeSenderDomainModal.propTypes = { + senderDomain: PropTypes.string.isRequired, +}; + +AuthorizeSenderDomainModal.defaultProps = { + setVerifiedSenderDomain: noop, +}; + +export { AuthorizeSenderDomainModal }; diff --git a/mailpoet/assets/js/src/common/manage_sender_domain/index.ts b/mailpoet/assets/js/src/common/manage_sender_domain/index.ts new file mode 100644 index 0000000000..e56036f824 --- /dev/null +++ b/mailpoet/assets/js/src/common/manage_sender_domain/index.ts @@ -0,0 +1,2 @@ +export * from './manage_sender_domain_types'; +export * from './manage_sender_domain'; diff --git a/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain.tsx b/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain.tsx new file mode 100644 index 0000000000..aff0381386 --- /dev/null +++ b/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain.tsx @@ -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; + loadingButton: boolean; + verifyDnsButtonClicked: () => void; +}; +function ManageSenderDomain({ + max_width, + rows, + loadingButton, + verifyDnsButtonClicked, +}: Props) { + if (rows.length === 0) return ; + + const { dns, domain } = rows[0]; + + return ( +
+ + {' '} + {MailPoet.I18n.t('manageSenderDomainHeaderTitle')}{' '} + +

+ {ReactStringReplace( + MailPoet.I18n.t('manageSenderDomainHeaderSubtitle'), + /\[link](.*?)\[\/link]/g, + (match) => ( + + {match} + + ), + )} +

+ + + + + + + + + + + + {dns.map((dnsRecord) => ( + + + + + + + ))} + +
{MailPoet.I18n.t('manageSenderDomainTableHeaderType')} {MailPoet.I18n.t('manageSenderDomainTableHeaderHost')} {MailPoet.I18n.t('manageSenderDomainTableHeaderValue')} {MailPoet.I18n.t('manageSenderDomainTableHeaderStatus')}
{dnsRecord.type}{dnsRecord.host}{dnsRecord.value}{dnsRecord.status}
+ +
+ ); +} + +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 }; diff --git a/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain_types.ts b/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain_types.ts new file mode 100644 index 0000000000..f580029f36 --- /dev/null +++ b/mailpoet/assets/js/src/common/manage_sender_domain/manage_sender_domain_types.ts @@ -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; +}; + +export { + SenderDomainVerificationStatus, + SenderDomainDnsItem, + SenderDomainEntity, +}; diff --git a/mailpoet/assets/js/src/common/sender_email_address_warning.jsx b/mailpoet/assets/js/src/common/sender_email_address_warning.jsx index d923d852e3..49179c44cb 100644 --- a/mailpoet/assets/js/src/common/sender_email_address_warning.jsx +++ b/mailpoet/assets/js/src/common/sender_email_address_warning.jsx @@ -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 - { setShowAuthorizedEmailModal(false); }} - setAuthorizedAddress={setAuthorizedEmailAddress} /> )}

@@ -80,7 +79,7 @@ function SenderEmailAddressWarning({ badRequest([ - APIError::BAD_REQUEST => WPFunctions::get()->__('failed sender domain verification', 'mailpoet'), + APIError::BAD_REQUEST => WPFunctions::get()->__($response['error'] ?? 'failed sender domain verification', 'mailpoet'), ], $response); } diff --git a/mailpoet/views/layout.html b/mailpoet/views/layout.html index 8d9af2b5c2..ab411a6b90 100644 --- a/mailpoet/views/layout.html +++ b/mailpoet/views/layout.html @@ -116,6 +116,14 @@ jQuery('#adminmenu #toplevel_page_mailpoet-newsletters') 'setFromAddressEmailNotAuthorized': __('Can’t 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 domain’s 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? They’re also a source of inspiration for our team.'), 'reviewRequestUsingForDays': _n('You’ve been using MailPoet for [days] day now, and we would love to read your own review.', 'You’ve been using MailPoet for [days] days now, and we would love to read your own review.', installed_days_ago),