Add error boundary to subscriber import/export app

[MAILPOET-4706]
This commit is contained in:
Sam Najian
2022-12-06 14:02:06 +01:00
committed by Aschepikov
parent 7fa694314b
commit 7fd11d4fb5
16 changed files with 110 additions and 71 deletions

View File

@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { withBoundary } from './error_boundary';
function ScrollToTopComponent({ children, location: { pathname } }) {
useEffect(() => {
@@ -9,4 +10,5 @@ function ScrollToTopComponent({ children, location: { pathname } }) {
return children || null;
}
export const ScrollToTop = withRouter(ScrollToTopComponent);
ScrollToTopComponent.displayName = 'ScrollToTopComponent';
export const ScrollToTop = withRouter(withBoundary(ScrollToTopComponent));

View File

@@ -1,7 +1,7 @@
import ReactDOM from 'react-dom';
import { FilterType, List, Logs } from './list';
import { ErrorBoundary } from 'common';
import { FilterType, List, Logs } from './list';
interface LogsWindow extends Window {
mailpoet_logs: Logs;

View File

@@ -1,14 +1,15 @@
import { useContext } from 'react';
import { GlobalContext } from 'context/index.jsx';
import { withBoundary } from 'common';
import { Notice } from './notice.tsx';
const Notices = () => {
const NoticesComponent = () => {
const { notices } = useContext(GlobalContext);
return notices.items.map(({ id, ...props }) => (
<Notice key={id} {...props} />
));
};
Notices.displayName = 'Notices';
NoticesComponent.displayName = 'Notices';
const Notices = withBoundary(NoticesComponent);
export { Notices };

View File

@@ -5,6 +5,7 @@ import { ScrollToTop } from 'common/scroll_to_top.jsx';
import { GlobalContext, useGlobalContextValue } from 'context/index.jsx';
import { Notices } from 'notices/notices.jsx';
import { withBoundary } from 'common';
import { StepMethodSelection } from './import/step_method_selection.jsx';
import { StepInputValidation } from './import/step_input_validation.jsx';
import { StepDataManipulation } from './import/step_data_manipulation.jsx';
@@ -28,7 +29,7 @@ function ImportSubscribers() {
<Switch>
<Route
path="/step_clean_list"
render={(props) => <StepCleanList {...props} />}
render={withBoundary(StepCleanList)}
/>
<Route
path="/step_method_selection"

View File

@@ -5,7 +5,7 @@ type Props = {
onProceed?: () => void;
};
export function CleanList({ onProceed }: Props): JSX.Element {
function CleanList({ onProceed }: Props): JSX.Element {
return (
<div className="mailpoet-clean-list-step-container">
<p>{MailPoet.I18n.t('cleanListText1')}</p>
@@ -26,3 +26,6 @@ export function CleanList({ onProceed }: Props): JSX.Element {
</div>
);
}
CleanList.displayName = 'CleanList';
export { CleanList };

View File

@@ -47,5 +47,5 @@ PreviousNextStepButtons.defaultProps = {
onPreviousAction: () => {},
onNextAction: () => {},
};
PreviousNextStepButtons.displayName = 'PreviousNextStepButtons';
export { PreviousNextStepButtons };

View File

@@ -1,8 +1,11 @@
import { RouteComponentProps } from 'react-router-dom';
import { CleanList } from './clean_list';
export function StepCleanList({ history }: RouteComponentProps): JSX.Element {
function StepCleanList({ history }: RouteComponentProps): JSX.Element {
return (
<CleanList onProceed={(): void => history.push('step_method_selection')} />
);
}
StepCleanList.displayName = 'StepCleanList';
export { StepCleanList };

View File

@@ -1,8 +1,9 @@
import { useEffect, useState, useCallback } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { CleanList } from 'subscribers/importExport/import/clean_list';
import { ErrorBoundary } from 'common';
import { InitialQuestion } from './step_input_validation/initial_question.jsx';
import { WrongSourceBlock } from './step_input_validation/wrong_source_block.jsx';
import { LastSentQuestion } from './step_input_validation/last_sent_question.jsx';
@@ -30,17 +31,23 @@ function StepInputValidationComponent({ stepMethodSelectionData, history }) {
return (
<>
{importSource === undefined && (
<InitialQuestion onSubmit={setImportSource} history={history} />
<ErrorBoundary>
<InitialQuestion onSubmit={setImportSource} history={history} />
</ErrorBoundary>
)}
{importSource === 'address-book' && <WrongSourceBlock />}
{importSource === 'existing-list' && lastSent === undefined && (
<LastSentQuestion onSubmit={lastSentSubmit} />
<ErrorBoundary>
<LastSentQuestion onSubmit={lastSentSubmit} />
</ErrorBoundary>
)}
{importSource === 'existing-list' && lastSent === 'notRecently' && (
<CleanList />
<ErrorBoundary>
<CleanList />
</ErrorBoundary>
)}
</>
);

View File

@@ -57,5 +57,5 @@ InitialQuestion.propTypes = {
}).isRequired,
onSubmit: PropTypes.func.isRequired,
};
InitialQuestion.displayName = 'InitialQuestion';
export { InitialQuestion };

View File

@@ -1,4 +1,4 @@
import { useState, useCallback } from 'react';
import { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
import { MailPoet } from 'mailpoet';
import { Button } from 'common/button/button';
@@ -63,5 +63,5 @@ function LastSentQuestion({ onSubmit }) {
LastSentQuestion.propTypes = {
onSubmit: PropTypes.func.isRequired,
};
LastSentQuestion.displayName = 'LastSentQuestion';
export { LastSentQuestion };

View File

@@ -2,6 +2,7 @@ import { useState } from 'react';
import { withRouter } from 'react-router-dom';
import PropTypes from 'prop-types';
import { MailPoet } from 'mailpoet';
import { ErrorBoundary } from 'common';
import { SelectImportMethod } from './step_method_selection/select_import_method.jsx';
import { MethodPaste } from './step_method_selection/method_paste.jsx';
import { MethodUpload } from './step_method_selection/method_upload.jsx';
@@ -57,41 +58,51 @@ function StepMethodSelectionComponent({
return (
<div className="mailpoet-settings-grid">
<SelectImportMethod activeMethod={method} onMethodChange={setMethod} />
<ErrorBoundary>
<SelectImportMethod activeMethod={method} onMethodChange={setMethod} />
</ErrorBoundary>
{method === 'paste-method' && (
<MethodPaste
onPrevious={previousStep}
onValueChange={setPastedCsvData}
onFinish={processLocal}
canFinish={!!pastedCsvData.trim()}
data={pastedCsvData}
/>
<ErrorBoundary>
<MethodPaste
onPrevious={previousStep}
onValueChange={setPastedCsvData}
onFinish={processLocal}
canFinish={!!pastedCsvData.trim()}
data={pastedCsvData}
/>
</ErrorBoundary>
)}
{method === 'file-method' && (
<MethodUpload
onPrevious={previousStep}
onValueChange={setFile}
onFinish={processLocal}
canFinish={!!file}
data={file}
/>
<ErrorBoundary>
<MethodUpload
onPrevious={previousStep}
onValueChange={setFile}
onFinish={processLocal}
canFinish={!!file}
data={file}
/>
</ErrorBoundary>
)}
{method === 'mailchimp-method' && (
<MethodMailChimp
onPrevious={previousStep}
onFinish={(data) => {
MailPoet.trackEvent('Subscribers import started', {
source: 'MailChimp',
});
finish(data);
}}
/>
<ErrorBoundary>
<MethodMailChimp
onPrevious={previousStep}
onFinish={(data) => {
MailPoet.trackEvent('Subscribers import started', {
source: 'MailChimp',
});
finish(data);
}}
/>
</ErrorBoundary>
)}
{method === undefined && (
<PreviousNextStepButtons
canGoNext={false}
onPreviousAction={previousStep}
/>
<ErrorBoundary>
<PreviousNextStepButtons
canGoNext={false}
onPreviousAction={previousStep}
/>
</ErrorBoundary>
)}
</div>
);
@@ -104,5 +115,5 @@ StepMethodSelectionComponent.propTypes = {
setStepMethodSelectionData: PropTypes.func.isRequired,
subscribersLimitForValidation: PropTypes.number.isRequired,
};
StepMethodSelectionComponent.diplayName = 'StepMethodSelection';
export const StepMethodSelection = withRouter(StepMethodSelectionComponent);

View File

@@ -160,4 +160,6 @@ MethodMailChimp.defaultProps = {
onPrevious: () => {},
};
MethodMailChimp.displayName = 'MethodMailChimp';
export { MethodMailChimp };

View File

@@ -70,5 +70,5 @@ MethodPaste.defaultProps = {
onPrevious: () => {},
data: '',
};
MethodPaste.displayName = 'MethodPaste';
export { MethodPaste };

View File

@@ -71,5 +71,5 @@ MethodUpload.defaultProps = {
onFinish: () => {},
onPrevious: () => {},
};
MethodUpload.displayName = 'MethodUpload';
export { MethodUpload };

View File

@@ -78,5 +78,5 @@ SelectImportMethod.propTypes = {
SelectImportMethod.defaultProps = {
activeMethod: undefined,
};
SelectImportMethod.displayName = 'SelectImportMethod';
export { SelectImportMethod };

View File

@@ -6,6 +6,7 @@ import { withRouter } from 'react-router-dom';
import ReactStringReplace from 'react-string-replace';
import { Button } from 'common/button/button';
import { ErrorBoundary } from 'common';
function ResultMessage({ subscribersCount, segments, initialMessage }) {
if (subscribersCount) {
@@ -33,6 +34,7 @@ ResultMessage.defaultProps = {
subscribersCount: 0,
initialMessage: '',
};
ResultMessage.displayName = 'ResultMessage';
function NoAction({ createdSubscribers, updatedSubscribers }) {
if (!createdSubscribers && !updatedSubscribers) {
@@ -50,6 +52,7 @@ NoAction.defaultProps = {
createdSubscribers: 0,
updatedSubscribers: 0,
};
NoAction.displayName = 'NoAction';
function SuppressionListReminder({ createdSubscribers, updatedSubscribers }) {
if (createdSubscribers || updatedSubscribers) {
@@ -91,6 +94,7 @@ SuppressionListReminder.defaultProps = {
createdSubscribers: 0,
updatedSubscribers: 0,
};
SuppressionListReminder.displayName = 'SuppressionListReminder';
function NoWelcomeEmail({ addedToSegmentWithWelcomeNotification }) {
if (addedToSegmentWithWelcomeNotification) {
@@ -106,6 +110,7 @@ NoWelcomeEmail.propTypes = {
NoWelcomeEmail.defaultProps = {
addedToSegmentWithWelcomeNotification: false,
};
NoWelcomeEmail.diplayName = 'NoWelcomeEmail';
function StepResultsComponent({
errors,
@@ -136,31 +141,35 @@ function StepResultsComponent({
}
return (
<>
<div className="updated">
<ResultMessage
subscribersCount={createdSubscribers}
segments={segments}
initialMessage={MailPoet.I18n.t('subscribersCreated')}
/>
<ResultMessage
subscribersCount={updatedSubscribers}
segments={segments}
initialMessage={MailPoet.I18n.t('subscribersUpdated')}
/>
<NoAction
<ErrorBoundary>
<div className="updated">
<ResultMessage
subscribersCount={createdSubscribers}
segments={segments}
initialMessage={MailPoet.I18n.t('subscribersCreated')}
/>
<ResultMessage
subscribersCount={updatedSubscribers}
segments={segments}
initialMessage={MailPoet.I18n.t('subscribersUpdated')}
/>
<NoAction
createdSubscribers={createdSubscribers}
updatedSubscribers={updatedSubscribers}
/>
<NoWelcomeEmail
addedToSegmentWithWelcomeNotification={
addedToSegmentWithWelcomeNotification
}
/>
</div>
</ErrorBoundary>
<ErrorBoundary>
<SuppressionListReminder
createdSubscribers={createdSubscribers}
updatedSubscribers={updatedSubscribers}
/>
<NoWelcomeEmail
addedToSegmentWithWelcomeNotification={
addedToSegmentWithWelcomeNotification
}
/>
</div>
<SuppressionListReminder
createdSubscribers={createdSubscribers}
updatedSubscribers={updatedSubscribers}
/>
</ErrorBoundary>
<div className="mailpoet-settings-grid">
<div className="mailpoet-settings-save">
<Button
@@ -203,5 +212,5 @@ StepResultsComponent.defaultProps = {
updatedSubscribers: undefined,
addedToSegmentWithWelcomeNotification: undefined,
};
StepResultsComponent.displayName = 'StepResultsComponent';
export const StepResults = withRouter(StepResultsComponent);