@@ -14,10 +14,12 @@ import {
1414 DialogTrigger ,
1515} from "@/components/ui/dialog" ;
1616import { Skeleton } from "@/components/ui/skeleton" ;
17+ import * as Sentry from "@sentry/nextjs" ;
1718import { useMutation } from "@tanstack/react-query" ;
1819import { TransactionButton } from "components/buttons/TransactionButton" ;
1920import { InfoIcon } from "lucide-react" ;
20- import { Suspense , useState } from "react" ;
21+ import { Suspense , useEffect , useState } from "react" ;
22+ import { ErrorBoundary , type FallbackProps } from "react-error-boundary" ;
2123import { toast } from "sonner" ;
2224import {
2325 type ContractOptions ,
@@ -98,24 +100,30 @@ export function ModuleCard(props: ModuleCardProps) {
98100 return (
99101 < >
100102 < Suspense fallback = { < GenericModuleLoadingSkeleton /> } >
101- < ModuleInstance
102- contract = { contract }
103- contractInfo = { {
104- name : contractInfo . name ,
105- description : contractInfo . description ,
106- publisher : contractInfo . publisher ,
107- version : contractInfo . version ,
108- } }
109- ownerAccount = { ownerAccount }
110- uninstallButton = { {
111- onClick : ( ) => {
112- setIsUninstallModalOpen ( true ) ;
113- } ,
114- isPending : uninstallMutation . isPending ,
115- } }
116- moduleAddress = { moduleAddress }
117- allModuleContractInfo = { props . allModuleContractInfo }
118- />
103+ < ErrorBoundary
104+ FallbackComponent = { ( p ) => (
105+ < ModuleErrorBoundary moduleName = { contractInfo . name } { ...p } />
106+ ) }
107+ >
108+ < ModuleInstance
109+ contract = { contract }
110+ contractInfo = { {
111+ name : contractInfo . name ,
112+ description : contractInfo . description ,
113+ publisher : contractInfo . publisher ,
114+ version : contractInfo . version ,
115+ } }
116+ ownerAccount = { ownerAccount }
117+ uninstallButton = { {
118+ onClick : ( ) => {
119+ setIsUninstallModalOpen ( true ) ;
120+ } ,
121+ isPending : uninstallMutation . isPending ,
122+ } }
123+ moduleAddress = { moduleAddress }
124+ allModuleContractInfo = { props . allModuleContractInfo }
125+ />
126+ </ ErrorBoundary >
119127 </ Suspense >
120128
121129 < Dialog
@@ -303,3 +311,25 @@ export function ModuleCardUI(props: ModuleCardUIProps) {
303311function GenericModuleLoadingSkeleton ( ) {
304312 return < Skeleton className = "h-[190px] w-full border border-border" /> ;
305313}
314+
315+ function ModuleErrorBoundary (
316+ props : FallbackProps & {
317+ moduleName : string ;
318+ } ,
319+ ) {
320+ // eslint-disable-next-line no-restricted-syntax
321+ useEffect ( ( ) => {
322+ Sentry . withScope ( ( scope ) => {
323+ scope . setTag ( "component-crashed" , "true" ) ;
324+ scope . setTag ( "component-crashed-boundary" , "ModuleErrorBoundary" ) ;
325+ scope . setLevel ( "fatal" ) ;
326+ Sentry . captureException ( props . error ) ;
327+ } ) ;
328+ } , [ props . error ] ) ;
329+
330+ return (
331+ < div className = "flex h-[190px] w-full items-center justify-center border border-border" >
332+ Failed to render { props . moduleName } module
333+ </ div >
334+ ) ;
335+ }
0 commit comments