diff --git a/package.json b/package.json index 52b1d6a61..a378bf5a1 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "qrcode.react": "^4.2.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-error-boundary": "^6.0.0", "react-i18next": "^16.4.0", "react-inspector": "^9.0.0", "tailwind-merge": "^3.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d67c4438f..69a28b941 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -107,6 +107,9 @@ importers: react-dom: specifier: ^19.0.0 version: 19.2.1(react@19.2.1) + react-error-boundary: + specifier: ^6.0.0 + version: 6.0.0(react@19.2.1) react-i18next: specifier: ^16.4.0 version: 16.4.0(i18next@25.7.1(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3) @@ -3752,6 +3755,11 @@ packages: peerDependencies: react: ^19.2.1 + react-error-boundary@6.0.0: + resolution: {integrity: sha512-gdlJjD7NWr0IfkPlaREN2d9uUZUlksrfOx7SX62VRerwXbMY6ftGCIZua1VG1aXFNOimhISsTq+Owp725b9SiA==} + peerDependencies: + react: '>=16.13.1' + react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} @@ -8042,6 +8050,11 @@ snapshots: react: 19.2.1 scheduler: 0.27.0 + react-error-boundary@6.0.0(react@19.2.1): + dependencies: + '@babel/runtime': 7.28.4 + react: 19.2.1 + react-fast-compare@3.2.2: {} react-i18next@16.4.0(i18next@25.7.1(typescript@5.9.3))(react-dom@19.2.1(react@19.2.1))(react@19.2.1)(typescript@5.9.3): diff --git a/src/components/common/error-boundary.tsx b/src/components/common/error-boundary.tsx new file mode 100644 index 000000000..4b0a7e6a8 --- /dev/null +++ b/src/components/common/error-boundary.tsx @@ -0,0 +1,46 @@ +import { ErrorBoundary as BaseErrorBoundary, useErrorBoundary } from 'react-error-boundary' +import type { ReactNode } from 'react' +import { Button } from '@/components/ui/button' + +interface ErrorFallbackProps { + error: Error + resetErrorBoundary: () => void +} + +function ErrorFallback({ error, resetErrorBoundary }: ErrorFallbackProps) { + return ( +
+
+

Something went wrong

+

+ {error.message || 'An unexpected error occurred'} +

+
+ +
+ ) +} + +interface ErrorBoundaryProps { + children: ReactNode + fallback?: ReactNode + onError?: (error: Error, info: React.ErrorInfo) => void +} + +export function ErrorBoundary({ children, fallback, onError }: ErrorBoundaryProps) { + return ( + <>{fallback} : ErrorFallback} + onError={(error, info) => { + console.error('ErrorBoundary caught an error:', error, info) + onError?.(error, info) + }} + > + {children} + + ) +} + +export { useErrorBoundary } diff --git a/src/components/common/index.ts b/src/components/common/index.ts index dfabfea9a..a184ef9ee 100644 --- a/src/components/common/index.ts +++ b/src/components/common/index.ts @@ -10,3 +10,4 @@ export { IconCircle } from './icon-circle' export { FormField } from './form-field' export { Alert } from './alert' export { StepIndicator, ProgressSteps } from './step-indicator' +export { ErrorBoundary } from './error-boundary' diff --git a/src/stackflow/activities/SendActivity.tsx b/src/stackflow/activities/SendActivity.tsx index 60e7b7fd7..32f8b8434 100644 --- a/src/stackflow/activities/SendActivity.tsx +++ b/src/stackflow/activities/SendActivity.tsx @@ -1,6 +1,7 @@ import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; import { ActivityParamsProvider } from "../hooks"; +import { ErrorBoundary } from "@/components/common/error-boundary"; import { SendPage } from "@/pages/send"; type SendParams = { @@ -13,7 +14,9 @@ export const SendActivity: ActivityComponentType = ({ params }) => { return ( - + + + ); diff --git a/src/stackflow/stackflow.ts b/src/stackflow/stackflow.ts index 87777bbe4..9490d5bc4 100644 --- a/src/stackflow/stackflow.ts +++ b/src/stackflow/stackflow.ts @@ -112,7 +112,9 @@ export const { Stack, useFlow, useStepFlow, activities } = stackflow({ WalletAddSheetActivity, SecurityWarningSheetActivity, }, - initialActivity: () => "MainTabsActivity", + // Note: Don't set initialActivity when using historySyncPlugin + // The plugin will determine the initial activity based on the URL + // and use fallbackActivity when no route matches }); export type ActivityName = keyof typeof activities; diff --git a/src/stores/wallet.ts b/src/stores/wallet.ts index 9ec22c769..f06cb0b0d 100644 --- a/src/stores/wallet.ts +++ b/src/stores/wallet.ts @@ -439,7 +439,11 @@ export const walletActions = { const adapter = registry.getAdapter(chain) if (!adapter) { - console.warn(`[refreshBalance] No adapter for chain: ${chain}`) + // This is expected during initialization or for unsupported chains + // Only log in development to avoid console noise + if (import.meta.env.DEV) { + console.debug(`[refreshBalance] Skipping chain without adapter: ${chain}`) + } return }