diff --git a/.changeset/error_page_with_report.md b/.changeset/error_page_with_report.md new file mode 100644 index 000000000..3a619c8d1 --- /dev/null +++ b/.changeset/error_page_with_report.md @@ -0,0 +1,5 @@ +--- +sable: minor +--- + +added error page making it easier to report errors when they occur in the field diff --git a/src/app/components/DefaultErrorPage.tsx b/src/app/components/DefaultErrorPage.tsx new file mode 100644 index 000000000..edf8acf5f --- /dev/null +++ b/src/app/components/DefaultErrorPage.tsx @@ -0,0 +1,116 @@ +import { Box, Button, Dialog, Icon, Icons, Text, color, config } from 'folds'; +import { SplashScreen } from '$components/splash-screen'; +import { buildGitHubUrl } from '$features/bug-report/BugReportModal'; + +type ErrorPageProps = { + error: Error; +}; + +function createIssueUrl(error: Error): string { + const stacktrace = error.stack || 'No stacktrace available'; + + const automatedBugReport = `# Automated Bug Report +Error occurred in the application. + +## Error Message +\`\`\` +${error.message} +\`\`\` + +## Stacktrace +\`\`\` +${stacktrace} +\`\`\``; + + return buildGitHubUrl('bug', `Error: ${error.message}`, { context: automatedBugReport }); +} + +// This component is used as the fallback for the ErrorBoundary in App.tsx, which means it will be rendered whenever an uncaught error is thrown in any of the child components and not handled locally. +// It provides a user-friendly error message and options to report the issue or reload the page. +// Motivation of the design is to encourage users to report issues while also providing them with the necessary information to do so, and to give them an easy way to recover by reloading the page. +// Note: Since this component is rendered in response to an error, it should be as resilient as possible and avoid any complex logic or dependencies that could potentially throw additional errors. +export function ErrorPage({ error }: ErrorPageProps) { + return ( + + + + + + + + Oops! Something went wrong + + + An unexpected error occurred. Please try again. If it continues, report the issue on + our GitHub using the button below, it will include error details to help us + investigate. Thank you for helping improve the app. + + + + + {error.message} + + + + {error.stack} + + + + + + + + + + ); +} diff --git a/src/app/features/bug-report/BugReportModal.tsx b/src/app/features/bug-report/BugReportModal.tsx index 1ee788f05..2f90fda31 100644 --- a/src/app/features/bug-report/BugReportModal.tsx +++ b/src/app/features/bug-report/BugReportModal.tsx @@ -52,7 +52,11 @@ async function searchSimilarIssues(query: string, signal: AbortSignal): Promise< // Field IDs match the ids defined in .github/ISSUE_TEMPLATE/bug_report.yml // and feature_request.yml so GitHub pre-fills each form field directly. -function buildGitHubUrl(type: ReportType, title: string, fields: Record): string { +export function buildGitHubUrl( + type: ReportType, + title: string, + fields: Record +): string { const devLabel = IS_RELEASE_TAG ? '' : '-dev'; const buildLabel = BUILD_HASH ? ` (${BUILD_HASH})` : ''; const version = `v${APP_VERSION}${devLabel}${buildLabel}`; diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx index bfe51f2a9..0408f38ea 100644 --- a/src/app/pages/App.tsx +++ b/src/app/pages/App.tsx @@ -3,11 +3,13 @@ import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProv import { RouterProvider } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ErrorBoundary } from 'react-error-boundary'; import { ClientConfigLoader } from '$components/ClientConfigLoader'; import { ClientConfigProvider } from '$hooks/useClientConfig'; import { ScreenSizeProvider, useScreenSize } from '$hooks/useScreenSize'; import { useCompositionEndTracking } from '$hooks/useComposingCheck'; +import { ErrorPage } from '$components/DefaultErrorPage'; import { ConfigConfigError, ConfigConfigLoading } from './ConfigConfig'; import { FeatureCheck } from './FeatureCheck'; import { createRouter } from './Router'; @@ -21,33 +23,35 @@ function App() { const portalContainer = document.getElementById('portalContainer') ?? undefined; return ( - - - - - - } - error={(err, retry, ignore) => ( - - )} - > - {(clientConfig) => ( - - - - - - - - - )} - - - - - - + + + + + + + } + error={(err, retry, ignore) => ( + + )} + > + {(clientConfig) => ( + + + + + + + + + )} + + + + + + + ); }