mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-27 21:03:29 +03:00
Added error boundaries to AdminX to avoid crashing the entire page (#18255)
refs https://github.com/TryGhost/Product/issues/3832
This commit is contained in:
parent
ffafec9690
commit
c572f2855d
@ -0,0 +1,22 @@
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
import type {Meta, StoryObj} from '@storybook/react';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Error Boundary',
|
||||
component: ErrorBoundary,
|
||||
tags: ['autodocs']
|
||||
} satisfies Meta<typeof ErrorBoundary>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ErrorBoundary>;
|
||||
|
||||
const RaisesError = () => {
|
||||
throw new Error('Something went wrong');
|
||||
};
|
||||
|
||||
export const WithError: Story = {
|
||||
args: {
|
||||
name: 'Test Section',
|
||||
children: <RaisesError />
|
||||
}
|
||||
};
|
@ -0,0 +1,50 @@
|
||||
import Banner from './Banner';
|
||||
import React, {ComponentType, ErrorInfo, ReactNode} from 'react';
|
||||
|
||||
/**
|
||||
* Catches errors in child components and displays a banner. Useful to prevent errors in one
|
||||
* section from crashing the entire page
|
||||
*/
|
||||
class ErrorBoundary extends React.Component<{children: ReactNode, name: ReactNode}> {
|
||||
state = {hasError: false};
|
||||
|
||||
constructor(props: {children: ReactNode, name: ReactNode}) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
static getDerivedStateFromError() {
|
||||
return {hasError: true};
|
||||
}
|
||||
|
||||
componentDidCatch(error: unknown, info: ErrorInfo) {
|
||||
// TODO: Log to Sentry
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('In component:', info.componentStack);
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<Banner color='red'>
|
||||
An error occurred loading {this.props.name}. Please refresh and try again.
|
||||
</Banner>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
|
||||
export const withErrorBoundary = <Props extends Record<string, unknown>>(Component: ComponentType<Props>, name: string) => {
|
||||
return function WithErrorBoundary(props: Props) {
|
||||
return (
|
||||
<ErrorBoundary name={name}>
|
||||
<Component {...props} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
};
|
@ -8,6 +8,7 @@ import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {ReactCodeMirrorRef} from '@uiw/react-codemirror';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const CodeInjection: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -93,4 +94,4 @@ const CodeInjection: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeInjection;
|
||||
export default withErrorBoundary(CodeInjection, 'Code injection');
|
||||
|
@ -2,6 +2,7 @@ import Button from '../../../admin-x-ds/global/Button';
|
||||
import React from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const History: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -21,4 +22,4 @@ const History: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default History;
|
||||
export default withErrorBoundary(History, 'History');
|
||||
|
@ -19,6 +19,7 @@ import {ReactComponent as ZapierIcon} from '../../../assets/icons/zapier.svg';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
interface IntegrationItemProps {
|
||||
icon?: React.ReactNode,
|
||||
@ -236,4 +237,4 @@ const Integrations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Integrations;
|
||||
export default withErrorBoundary(Integrations, 'Integrations');
|
||||
|
@ -8,6 +8,7 @@ import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import SettingGroupHeader from '../../../admin-x-ds/settings/SettingGroupHeader';
|
||||
import TabView, {Tab} from '../../../admin-x-ds/global/TabView';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
type LabsTab = 'labs-migration-options' | 'labs-alpha-features' | 'labs-beta-features';
|
||||
|
||||
@ -66,4 +67,4 @@ const Labs: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Labs;
|
||||
export default withErrorBoundary(Labs, 'Labs');
|
||||
|
@ -10,6 +10,7 @@ import {getSettingValues} from '../../../api/settings';
|
||||
import {useBrowseLabels} from '../../../api/labels';
|
||||
import {useBrowseOffers} from '../../../api/offers';
|
||||
import {useBrowseTiers} from '../../../api/tiers';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
type RefipientValueArgs = {
|
||||
defaultEmailRecipients: string;
|
||||
@ -206,4 +207,4 @@ const DefaultRecipients: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultRecipients;
|
||||
export default withErrorBoundary(DefaultRecipients, 'Default recipients');
|
||||
|
@ -7,6 +7,7 @@ import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {Setting, getSettingValues, useEditSettings} from '../../../api/settings';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const EnableNewsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {settings} = useGlobalData();
|
||||
@ -75,4 +76,4 @@ const EnableNewsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
</SettingGroup>);
|
||||
};
|
||||
|
||||
export default EnableNewsletters;
|
||||
export default withErrorBoundary(EnableNewsletters, 'Newsletter sending');
|
||||
|
@ -7,6 +7,7 @@ import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupConten
|
||||
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getSettingValues, useEditSettings} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const MAILGUN_REGIONS = [
|
||||
{label: '🇺🇸 US', value: 'https://api.mailgun.net/v3'},
|
||||
@ -122,4 +123,4 @@ const MailGun: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MailGun;
|
||||
export default withErrorBoundary(MailGun, 'Mailgun');
|
||||
|
@ -5,6 +5,7 @@ import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {useBrowseNewsletters} from '../../../api/newsletters';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Newsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -46,4 +47,4 @@ const Newsletters: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Newsletters;
|
||||
export default withErrorBoundary(Newsletters, 'Newsletters');
|
||||
|
@ -8,6 +8,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {ReactComponent as FacebookLogo} from '../../../admin-x-ds/assets/images/facebook-logo.svg';
|
||||
import {getImageUrl, useUploadImage} from '../../../api/images';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -141,4 +142,4 @@ const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Facebook;
|
||||
export default withErrorBoundary(Facebook, 'Facebook card');
|
||||
|
@ -7,6 +7,7 @@ import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -108,4 +109,4 @@ const LockSite: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LockSite;
|
||||
export default withErrorBoundary(LockSite, 'Make site private');
|
||||
|
@ -7,6 +7,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {ReactComponent as GoogleLogo} from '../../../admin-x-ds/assets/images/google-logo.svg';
|
||||
import {ReactComponent as MagnifyingGlass} from '../../../admin-x-ds/assets/icons/magnifying-glass.svg';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
interface SearchEnginePreviewProps {
|
||||
title: string;
|
||||
@ -126,4 +127,4 @@ const Metadata: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Metadata;
|
||||
export default withErrorBoundary(Metadata, 'Meta data');
|
||||
|
@ -4,6 +4,7 @@ import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupConten
|
||||
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -71,4 +72,4 @@ const PublicationLanguage: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PublicationLanguage;
|
||||
export default withErrorBoundary(PublicationLanguage, 'Publication language');
|
||||
|
@ -5,6 +5,7 @@ import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import validator from 'validator';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
function validateFacebookUrl(newUrl: string) {
|
||||
const errMessage = 'The URL must be in a format like https://www.facebook.com/yourPage';
|
||||
@ -87,10 +88,10 @@ const SocialAccounts: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
|
||||
const twitterInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [facebookHandle, twitterHandle] = getSettingValues(localSettings, ['facebook', 'twitter']) as string[];
|
||||
const [facebookHandle, twitterHandle] = getSettingValues<string | null>(localSettings, ['facebook', 'twitter']);
|
||||
|
||||
const [facebookUrl, setFacebookUrl] = useState(facebookHandleToUrl(facebookHandle));
|
||||
const [twitterUrl, setTwitterUrl] = useState(twitterHandleToUrl(twitterHandle));
|
||||
const [facebookUrl, setFacebookUrl] = useState(facebookHandle ? facebookHandleToUrl(facebookHandle) : '');
|
||||
const [twitterUrl, setTwitterUrl] = useState(twitterHandle ? twitterHandleToUrl(twitterHandle) : '');
|
||||
|
||||
const values = (
|
||||
<SettingGroupContent
|
||||
@ -199,4 +200,4 @@ const SocialAccounts: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default SocialAccounts;
|
||||
export default withErrorBoundary(SocialAccounts, 'Social accounts');
|
||||
|
@ -6,6 +6,7 @@ import timezoneData from '@tryghost/timezone-data';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getLocalTime} from '../../../utils/helpers';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
interface TimezoneDataDropdownOption {
|
||||
name: string;
|
||||
@ -100,4 +101,4 @@ const TimeZone: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TimeZone;
|
||||
export default withErrorBoundary(TimeZone, 'Site timezone');
|
||||
|
@ -4,6 +4,7 @@ import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupConten
|
||||
import TextField from '../../../admin-x-ds/global/form/TextField';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -82,4 +83,4 @@ const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TitleAndDescription;
|
||||
export default withErrorBoundary(TitleAndDescription, 'Title & description');
|
||||
|
@ -8,6 +8,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {ReactComponent as TwitterLogo} from '../../../admin-x-ds/assets/images/twitter-logo.svg';
|
||||
import {getImageUrl, useUploadImage} from '../../../api/images';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -141,4 +142,4 @@ const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Twitter;
|
||||
export default withErrorBoundary(Twitter, 'Twitter card');
|
||||
|
@ -14,6 +14,7 @@ import {UserInvite, useAddInvite, useDeleteInvite} from '../../../api/invites';
|
||||
import {generateAvatarColor, getInitials} from '../../../utils/helpers';
|
||||
import {showToast} from '../../../admin-x-ds/global/Toast';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
interface OwnerProps {
|
||||
user: User;
|
||||
@ -258,4 +259,4 @@ const Users: React.FC<{ keywords: string[], highlight?: boolean }> = ({keywords,
|
||||
);
|
||||
};
|
||||
|
||||
export default Users;
|
||||
export default withErrorBoundary(Users, 'Staff');
|
||||
|
@ -8,6 +8,7 @@ import {GroupBase, MultiValue} from 'react-select';
|
||||
import {getOptionLabel} from '../../../utils/helpers';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {useBrowseTiers} from '../../../api/tiers';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const MEMBERS_SIGNUP_ACCESS_OPTIONS = [
|
||||
{
|
||||
@ -190,4 +191,4 @@ const Access: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Access;
|
||||
export default withErrorBoundary(Access, 'Access');
|
||||
|
@ -6,6 +6,7 @@ import Toggle from '../../../admin-x-ds/global/form/Toggle';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {usePostsExports} from '../../../api/posts';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -105,4 +106,4 @@ const Analytics: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Analytics;
|
||||
export default withErrorBoundary(Analytics, 'Analytics');
|
||||
|
@ -2,6 +2,7 @@ import Button from '../../../admin-x-ds/global/Button';
|
||||
import React from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Portal: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -22,4 +23,4 @@ const Portal: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Portal;
|
||||
export default withErrorBoundary(Portal, 'Portal settings');
|
||||
|
@ -8,6 +8,7 @@ import useRouting from '../../../hooks/useRouting';
|
||||
import {Tier, getActiveTiers, getArchivedTiers, useBrowseTiers} from '../../../api/tiers';
|
||||
import {checkStripeEnabled} from '../../../api/settings';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const StripeConnectedButton: React.FC<{className?: string; onClick: () => void;}> = ({className, onClick}) => {
|
||||
className = clsx(
|
||||
@ -83,4 +84,4 @@ const Tiers: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Tiers;
|
||||
export default withErrorBoundary(Tiers, 'Tiers');
|
||||
|
@ -9,6 +9,7 @@ import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {confirmIfDirty} from '../../../utils/modals';
|
||||
import {currencySelectGroups, getSymbol, validateCurrencyAmount} from '../../../utils/currency';
|
||||
import {getSettingValues} from '../../../api/settings';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const TipsOrDonations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -132,4 +133,4 @@ const TipsOrDonations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default TipsOrDonations;
|
||||
export default withErrorBoundary(TipsOrDonations, 'Tips or donations');
|
||||
|
@ -2,6 +2,7 @@ import Button from '../../../admin-x-ds/global/Button';
|
||||
import React from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const AnnouncementBar: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -21,4 +22,4 @@ const AnnouncementBar: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AnnouncementBar;
|
||||
export default withErrorBoundary(AnnouncementBar, 'Announcement bar');
|
||||
|
@ -2,6 +2,7 @@ import Button from '../../../admin-x-ds/global/Button';
|
||||
import React from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const DesignSetting: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -21,4 +22,4 @@ const DesignSetting: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default DesignSetting;
|
||||
export default withErrorBoundary(DesignSetting, 'Branding and design');
|
||||
|
@ -2,6 +2,7 @@ import Button from '../../../admin-x-ds/global/Button';
|
||||
import React from 'react';
|
||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Navigation: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
@ -21,4 +22,4 @@ const Navigation: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
export default withErrorBoundary(Navigation, 'Navigation');
|
||||
|
@ -8,6 +8,7 @@ import TabView from '../../../admin-x-ds/global/TabView';
|
||||
import useRouting from '../../../hooks/useRouting';
|
||||
import useSettingGroup from '../../../hooks/useSettingGroup';
|
||||
import {useBrowseRecommendations} from '../../../api/recommendations';
|
||||
import {withErrorBoundary} from '../../../admin-x-ds/global/ErrorBoundary';
|
||||
|
||||
const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
const {
|
||||
@ -74,4 +75,4 @@ const Recommendations: React.FC<{ keywords: string[] }> = ({keywords}) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default Recommendations;
|
||||
export default withErrorBoundary(Recommendations, 'Recommendations');
|
||||
|
Loading…
Reference in New Issue
Block a user