Skip to content

Commit 589e978

Browse files
jonwaldsteinJon Waldstein
and
Jon Waldstein
authored
Feature: add error boundary to campaign details page (#7867)
Co-authored-by: Jon Waldstein <[email protected]>
1 parent 7cae308 commit 589e978

File tree

6 files changed

+201
-114
lines changed

6 files changed

+201
-114
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {__} from "@wordpress/i18n";
2+
import styles from "./styles.module.scss";
3+
4+
/**
5+
* @unrestricted
6+
*/
7+
export default function Fallback({error, resetErrorBoundary}) {
8+
return (
9+
<div role="alert" className={styles.errorBoundary}>
10+
<p className={styles.errorBoundaryParagraph}>
11+
{__(
12+
'An error occurred. The error message is:',
13+
'give'
14+
)}
15+
</p>
16+
<pre className={styles.errorBoundaryPre}>{error.message}</pre>
17+
<button type="button" onClick={resetErrorBoundary} className={styles.errorBoundaryButton}>
18+
{__('Reload page', 'give')}
19+
</button>
20+
</div>
21+
);
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import {ErrorBoundary} from 'react-error-boundary';
2+
import Fallback from './Fallback';
3+
4+
export default function CampaignDetailsErrorBoundary({children}) {
5+
return (
6+
<ErrorBoundary
7+
FallbackComponent={Fallback}
8+
onReset={() => {
9+
window.location.reload();
10+
}}
11+
>
12+
{children}
13+
</ErrorBoundary>
14+
);
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.errorBoundary {
2+
background-color: #fdecea;
3+
color: #611a15;
4+
border: 1px solid #f5c6cb;
5+
border-radius: 6px;
6+
padding: 1rem;
7+
margin: 1rem 0;
8+
font-family: sans-serif;
9+
}
10+
11+
.errorBoundaryParagraph {
12+
margin: 0 0 0.5rem 0;
13+
font-weight: bold;
14+
}
15+
16+
.errorBoundaryPre {
17+
background-color: #fff5f5;
18+
color: #c00;
19+
border: 1px solid #f1b0b7;
20+
padding: 0.5rem;
21+
border-radius: 4px;
22+
white-space: pre-wrap;
23+
word-break: break-word;
24+
}
25+
26+
.errorBoundaryButton {
27+
background-color: #c82333;
28+
color: white;
29+
border: none;
30+
border-radius: 4px;
31+
padding: 0.5rem 1rem;
32+
cursor: pointer;
33+
margin-top: 0.5rem;
34+
transition: background-color 0.3s;
35+
&:hover {
36+
background-color: #a71d2a;
37+
}
38+
}

src/Campaigns/resources/admin/components/CampaignDetailsPage/Components/CampaignStats/index.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import DateRangeFilters from './DateRangeFilters';
1212
import {amountFormatter, getCampaignOptionsWindowData, useCampaignEntityRecord} from '@givewp/campaigns/utils';
1313
import type {CampaignOverViewStat} from './types';
1414
import styles from './styles.module.scss';
15+
import CampaignDetailsErrorBoundary
16+
from '@givewp/campaigns/admin/components/CampaignDetailsPage/Components/CampaignDetailsErrorBoundary';
1517

1618
const campaignId = new URLSearchParams(window.location.search).get('id');
1719

@@ -100,15 +102,19 @@ const CampaignStats = () => {
100102
{__('This graph shows revenue for the campaign over its lifetime.', 'give')}
101103
</HeaderSubText>
102104
</header>
103-
<RevenueChart />
105+
<CampaignDetailsErrorBoundary>
106+
<RevenueChart />
107+
</CampaignDetailsErrorBoundary>
104108
</div>
105109
<div className={styles.nestedGrid}>
106110
<div className={styles.progressWidget}>
107111
<header className={styles.headerSpacing}>
108112
<HeaderText>{__('Goal progress', 'give')}</HeaderText>
109113
<HeaderSubText>{__('This chart shows your campaign goal progress.', 'give')}</HeaderSubText>
110114
</header>
111-
<GoalProgressChart value={campaign.goalStats.actual} goal={campaign.goal} goalType={campaign.goalType} />
115+
<CampaignDetailsErrorBoundary>
116+
<GoalProgressChart value={campaign.goalStats.actual} goal={campaign.goal} goalType={campaign.goalType} />
117+
</CampaignDetailsErrorBoundary>
112118
</div>
113119
<DefaultFormWidget defaultForm={campaign.defaultFormTitle} />
114120
</div>

src/Campaigns/resources/admin/components/CampaignDetailsPage/Tabs/index.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {CampaignDetailsTab} from '../types';
66
import styles from '../CampaignDetailsPage.module.scss';
77
import tabsDefinitions from './definitions';
88
import NotificationsPlaceholder from '../../Notifications';
9+
import CampaignDetailsErrorBoundary from '../Components/CampaignDetailsErrorBoundary';
910

1011
const tabs: CampaignDetailsTab[] = tabsDefinitions;
1112

@@ -79,9 +80,11 @@ export default function CampaignDetailsTabs() {
7980

8081
<div className={`${styles.pageContent} ${activeTab.fullwidth ? styles.fullWidth : ''}`}>
8182
{Object.values(tabs).map((tab) => (
82-
<TabPanel key={tab.id} id={tab.id}>
83-
<tab.content />
84-
</TabPanel>
83+
<CampaignDetailsErrorBoundary>
84+
<TabPanel key={tab.id} id={tab.id}>
85+
<tab.content />
86+
</TabPanel>
87+
</CampaignDetailsErrorBoundary>
8588
))}
8689
</div>
8790
</Tabs>

src/Campaigns/resources/admin/components/CampaignDetailsPage/index.tsx

+112-109
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {createCampaignPage, getCampaignOptionsWindowData, useCampaignEntityRecor
1919

2020
import styles from './CampaignDetailsPage.module.scss';
2121
import {useEntityRecord} from '@wordpress/core-data';
22+
import CampaignDetailsErrorBoundary from '@givewp/campaigns/admin/components/CampaignDetailsPage/Components/CampaignDetailsErrorBoundary';
2223

2324
interface Show {
2425
contextMenu?: boolean;
@@ -251,123 +252,125 @@ export default function CampaignsDetailsPage({campaignId}) {
251252
}
252253

253254
return (
254-
<FormProvider {...methods}>
255-
<form onSubmit={handleSubmit(onSubmit)}>
256-
<article className={`interface-interface-skeleton__content ${styles.page}`}>
257-
<header className={styles.pageHeader}>
258-
<div className={styles.breadcrumb}>
259-
<a href={`${adminUrl}edit.php?post_type=give_forms&page=give-campaigns`}>
260-
{__('Campaigns', 'give')}
261-
</a>
262-
<BreadcrumbSeparatorIcon />
263-
<span>{campaign.title}</span>
264-
</div>
265-
<div className={styles.flexContainer}>
266-
<div className={styles.flexRow}>
267-
<h1 className={styles.pageTitle}>{campaign.title}</h1>
268-
<StatusBadge status={campaign.status} />
255+
<CampaignDetailsErrorBoundary>
256+
<FormProvider {...methods}>
257+
<form onSubmit={handleSubmit(onSubmit)}>
258+
<article className={`interface-interface-skeleton__content ${styles.page}`}>
259+
<header className={styles.pageHeader}>
260+
<div className={styles.breadcrumb}>
261+
<a href={`${adminUrl}edit.php?post_type=give_forms&page=give-campaigns`}>
262+
{__('Campaigns', 'give')}
263+
</a>
264+
<BreadcrumbSeparatorIcon />
265+
<span>{campaign.title}</span>
269266
</div>
267+
<div className={styles.flexContainer}>
268+
<div className={styles.flexRow}>
269+
<h1 className={styles.pageTitle}>{campaign.title}</h1>
270+
<StatusBadge status={campaign.status} />
271+
</div>
272+
273+
<div className={`${styles.flexRow} ${styles.justifyContentEnd}`}>
274+
{!isCreatingPage && campaign.pageId > 0 ? (
275+
<a
276+
className={`button button-secondary ${styles.editCampaignPageButton}`}
277+
href={`${adminUrl}post.php?post=${campaign.pageId}&action=edit`}
278+
rel="noopener noreferrer"
279+
>
280+
{__('Edit campaign page', 'give')}
281+
</a>
282+
) : (
283+
<button
284+
type="button"
285+
className={`button button-tertiary ${styles.createCampaignPageButton}`}
286+
onClick={handleCampaignPageCreation}
287+
disabled={isCreatingPage}
288+
>
289+
{isCreatingPage
290+
? __('Creating Campaign Page', 'give')
291+
: __('Create Campaign Page', 'give')}
292+
</button>
293+
)}
270294

271-
<div className={`${styles.flexRow} ${styles.justifyContentEnd}`}>
272-
{!isCreatingPage && campaign.pageId > 0 ? (
273-
<a
274-
className={`button button-secondary ${styles.editCampaignPageButton}`}
275-
href={`${adminUrl}post.php?post=${campaign.pageId}&action=edit`}
276-
rel="noopener noreferrer"
295+
<button
296+
type="submit"
297+
disabled={campaign.status !== 'draft' && !formState.isDirty}
298+
className={`button button-primary ${styles.updateCampaignButton}`}
299+
onClick={(e) => {
300+
setValue('status', 'active', {shouldDirty: true});
301+
}}
277302
>
278-
{__('Edit campaign page', 'give')}
279-
</a>
280-
) : (
303+
{isSaving === 'active' ? (
304+
<>
305+
{__('Updating campaign', 'give')}
306+
<Spinner />
307+
</>
308+
) : (
309+
__('Update campaign', 'give')
310+
)}
311+
</button>
312+
281313
<button
282-
type="button"
283-
className={`button button-tertiary ${styles.createCampaignPageButton}`}
284-
onClick={handleCampaignPageCreation}
285-
disabled={isCreatingPage}
314+
className={`button button-secondary ${styles.campaignButtonDots}`}
315+
onClick={(e) => {
316+
e.preventDefault();
317+
setShow({contextMenu: !show.contextMenu});
318+
}}
286319
>
287-
{isCreatingPage
288-
? __('Creating Campaign Page', 'give')
289-
: __('Create Campaign Page', 'give')}
320+
<DotsIcons />
290321
</button>
291-
)}
292-
293-
<button
294-
type="submit"
295-
disabled={campaign.status !== 'draft' && !formState.isDirty}
296-
className={`button button-primary ${styles.updateCampaignButton}`}
297-
onClick={(e) => {
298-
setValue('status', 'active', {shouldDirty: true});
299-
}}
300-
>
301-
{isSaving === 'active' ? (
302-
<>
303-
{__('Updating campaign', 'give')}
304-
<Spinner />
305-
</>
306-
) : (
307-
__('Update campaign', 'give')
322+
323+
{!isSaving && show.contextMenu && (
324+
<div className={styles.contextMenu}>
325+
{campaign.pagePermalink && (
326+
<a
327+
href={campaign.pagePermalink}
328+
aria-label={__('View Campaign', 'give')}
329+
className={styles.contextMenuItem}
330+
>
331+
<ViewIcon /> {__('View Campaign', 'give')}
332+
</a>
333+
)}
334+
{campaign.status === 'archived' ? (
335+
<a
336+
href="#"
337+
className={cx(styles.contextMenuItem, styles.draft)}
338+
onClick={() => {
339+
updateStatus('draft');
340+
dispatch.dismissNotification('update-archive-notice');
341+
}}
342+
>
343+
<ArrowReverse /> {__('Move to draft', 'give')}
344+
</a>
345+
) : (
346+
<a
347+
href="#"
348+
className={cx(styles.contextMenuItem, styles.archive)}
349+
onClick={() => setShow({confirmationModal: true})}
350+
>
351+
<TrashIcon /> {__('Archive Campaign', 'give')}
352+
</a>
353+
)}
354+
</div>
308355
)}
309-
</button>
310-
311-
<button
312-
className={`button button-secondary ${styles.campaignButtonDots}`}
313-
onClick={(e) => {
314-
e.preventDefault();
315-
setShow({contextMenu: !show.contextMenu});
316-
}}
317-
>
318-
<DotsIcons />
319-
</button>
320-
321-
{!isSaving && show.contextMenu && (
322-
<div className={styles.contextMenu}>
323-
{campaign.pagePermalink && (
324-
<a
325-
href={campaign.pagePermalink}
326-
aria-label={__('View Campaign', 'give')}
327-
className={styles.contextMenuItem}
328-
>
329-
<ViewIcon /> {__('View Campaign', 'give')}
330-
</a>
331-
)}
332-
{campaign.status === 'archived' ? (
333-
<a
334-
href="#"
335-
className={cx(styles.contextMenuItem, styles.draft)}
336-
onClick={() => {
337-
updateStatus('draft');
338-
dispatch.dismissNotification('update-archive-notice');
339-
}}
340-
>
341-
<ArrowReverse /> {__('Move to draft', 'give')}
342-
</a>
343-
) : (
344-
<a
345-
href="#"
346-
className={cx(styles.contextMenuItem, styles.archive)}
347-
onClick={() => setShow({confirmationModal: true})}
348-
>
349-
<TrashIcon /> {__('Archive Campaign', 'give')}
350-
</a>
351-
)}
352-
</div>
353-
)}
356+
</div>
354357
</div>
355-
</div>
356-
</header>
357-
<Tabs />
358-
<ArchiveCampaignDialog
359-
title={__('Archive Campaign', 'give')}
360-
isOpen={show.confirmationModal}
361-
handleClose={() => setShow({confirmationModal: false, contextMenu: false})}
362-
handleConfirm={() => {
363-
updateStatus('archived');
364-
dispatch.dismissNotification('update-campaign-draft-page-notice');
365-
}}
366-
/>
367-
</article>
368-
</form>
369-
<NotificationPlaceholder type="snackbar" />;
370-
</FormProvider>
358+
</header>
359+
<Tabs />
360+
<ArchiveCampaignDialog
361+
title={__('Archive Campaign', 'give')}
362+
isOpen={show.confirmationModal}
363+
handleClose={() => setShow({confirmationModal: false, contextMenu: false})}
364+
handleConfirm={() => {
365+
updateStatus('archived');
366+
dispatch.dismissNotification('update-campaign-draft-page-notice');
367+
}}
368+
/>
369+
</article>
370+
</form>
371+
<NotificationPlaceholder type="snackbar" />;
372+
</FormProvider>
373+
</CampaignDetailsErrorBoundary>
371374
);
372375
}
373376

0 commit comments

Comments
 (0)