diff --git a/src/components/Configure/ErrorBoundary.tsx b/src/components/Configure/ErrorBoundary.tsx new file mode 100644 index 000000000..4552003a6 --- /dev/null +++ b/src/components/Configure/ErrorBoundary.tsx @@ -0,0 +1,66 @@ +/* eslint-disable react/prop-types */ +import React, { Component, ReactNode } from "react"; + +import { ComponentContainerError } from "./ComponentContainer"; + +interface ErrorBoundaryProps { + children: ReactNode; + fallback?: ReactNode; + onError?: (error: Error, errorInfo: React.ErrorInfo) => void; +} + +interface ErrorBoundaryState { + hasError: boolean; + error: Error | null; +} + +/** + * React Error Boundary component that catches JavaScript errors anywhere in the child component tree + * and displays a fallback UI instead of crashing the whole application. + * + * This is especially important for the InstallIntegration component to prevent errors in hooks + * or rendering from crashing the parent application. + */ +export class InstallIntegrationErrorBoundary extends Component< + ErrorBoundaryProps, + ErrorBoundaryState +> { + constructor(props: ErrorBoundaryProps) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error): ErrorBoundaryState { + // Update state so the next render will show the fallback UI + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void { + // Log error to console for debugging + console.error("InstallIntegration Error Boundary caught an error:", error); + console.error("[Error Info]:", errorInfo); + + // Call optional error callback + this.props.onError?.(error, errorInfo); + } + + render(): ReactNode { + if (this.state.hasError) { + // Use custom fallback if provided, otherwise use default + if (this.props.fallback) { + return this.props.fallback; + } + + return ( + + ); + } + + return this.props.children; + } +} diff --git a/src/components/Configure/InstallIntegration.tsx b/src/components/Configure/InstallIntegration.tsx index af51ce3b5..6e13fb13e 100644 --- a/src/components/Configure/InstallIntegration.tsx +++ b/src/components/Configure/InstallIntegration.tsx @@ -13,6 +13,7 @@ import { ComponentContainerLoading, } from "./ComponentContainer"; import { InstallationContent } from "./content/InstallationContent"; +import { InstallIntegrationErrorBoundary } from "./ErrorBoundary"; import { ConditionalHasConfigurationLayout } from "./layout/ConditionalHasConfigurationLayout/ConditionalHasConfigurationLayout"; import { ProtectedConnectionLayout } from "./layout/ProtectedConnectionLayout"; import { ObjectManagementNav } from "./nav/ObjectManagementNav"; @@ -165,15 +166,22 @@ export function InstallIntegration({ }; return ( - // eventually will use the headless providers for integration, consumer, and group etc - + } > - - + {/* eventually will use the headless providers for integration, consumer, and group etc */} + + + + ); } diff --git a/src/components/Configure/content/ConfigureInstallationBase.tsx b/src/components/Configure/content/ConfigureInstallationBase.tsx index e637eb135..b63b72c67 100644 --- a/src/components/Configure/content/ConfigureInstallationBase.tsx +++ b/src/components/Configure/content/ConfigureInstallationBase.tsx @@ -127,9 +127,10 @@ export function ConfigureInstallationBase({ const isStateNew = isModified || isCreateMode || isSelectedReadObjectComplete; // if the selected read object has an error in the manifest, it should not be saved - const isSelectedReadObjectError = !!hydratedRevision?.content?.read?.objects?.find( - (obj) => obj.objectName === selectedObjectName, - )?.error; + const isSelectedReadObjectError = + !!hydratedRevision?.content?.read?.objects?.find( + (obj) => obj.objectName === selectedObjectName, + )?.error; // should the save button be disabled? const isDisabled = diff --git a/src/components/Configure/index.ts b/src/components/Configure/index.ts index ae00fa05c..05f1b8fad 100644 --- a/src/components/Configure/index.ts +++ b/src/components/Configure/index.ts @@ -1 +1,2 @@ export { InstallIntegration } from "./InstallIntegration"; +export { InstallIntegrationErrorBoundary } from "./ErrorBoundary"; diff --git a/src/headless/installation/useCreateInstallation.ts b/src/headless/installation/useCreateInstallation.ts index 34f58a7cb..32f56ba3c 100644 --- a/src/headless/installation/useCreateInstallation.ts +++ b/src/headless/installation/useCreateInstallation.ts @@ -50,7 +50,9 @@ export function useCreateInstallation() { onSettled?: () => void; }) => { if (installation) { - const error = new Error("Installation already created. Try updating instead."); + const error = new Error( + "Installation already created. Try updating instead.", + ); onError?.(error); onSettled?.(); return;