From bacffbe64871a3d3bbd22a403b3ad9c80801d08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?nicole=20miko=C5=82ajczyk?= Date: Wed, 7 Jan 2026 22:21:21 +0100 Subject: [PATCH] pl-fe: add missing file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- packages/pl-fe/src/components/site-error.tsx | 217 +++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 packages/pl-fe/src/components/site-error.tsx diff --git a/packages/pl-fe/src/components/site-error.tsx b/packages/pl-fe/src/components/site-error.tsx new file mode 100644 index 000000000..02fed7fc7 --- /dev/null +++ b/packages/pl-fe/src/components/site-error.tsx @@ -0,0 +1,217 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { NODE_ENV } from 'pl-fe/build-config'; +import HStack from 'pl-fe/components/ui/hstack'; +import Stack from 'pl-fe/components/ui/stack'; +import Text from 'pl-fe/components/ui/text'; +import Textarea from 'pl-fe/components/ui/textarea'; +import { useLogo } from 'pl-fe/hooks/use-logo'; +import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config'; +import { captureSentryException } from 'pl-fe/sentry'; +import KVStore from 'pl-fe/storage/kv-store'; +import sourceCode from 'pl-fe/utils/code'; +import { isNetworkError } from 'pl-fe/utils/errors'; +import { unregisterSW } from 'pl-fe/utils/sw'; + +import SentryFeedbackForm from './sentry-feedback-form'; +import SiteLogo from './site-logo'; +import Column from './ui/column'; + +import type { ErrorRouteComponent } from '@tanstack/react-router'; + +const messages = defineMessages({ + networkErrorTitle: { id: 'bundle_column_error.title', defaultMessage: 'Network error' }, + networkErrorRetry: { id: 'bundle_column_error.retry', defaultMessage: 'Try again' }, +}); + +/** Application-level error boundary. Fills the whole screen. */ +const SiteError: ErrorRouteComponent = ({ error, info }) => { + const intl = useIntl(); + const { links, sentryDsn } = usePlFeConfig(); + const { src: logoSrc } = useLogo(); + const textarea = useRef(null); + + const [browser, setBrowser] = useState(); + const [sentryEventId, setSentryEventId] = useState(); + + const sentryEnabled = Boolean(sentryDsn); + const isProduction = NODE_ENV === 'production'; + const errorText = String(error) + info?.componentStack; + + const clearCookies: React.MouseEventHandler = (e) => { + localStorage.clear(); + sessionStorage.clear(); + KVStore.clear(); + + if ('serviceWorker' in navigator) { + e.preventDefault(); + unregisterSW().then(goHome).catch(goHome); + } + }; + + const handleCopy: React.MouseEventHandler = () => { + if (!textarea.current) return; + + textarea.current.select(); + textarea.current.setSelectionRange(0, 99999); + + document.execCommand('copy'); + }; + + useEffect(() => { + captureSentryException(error, { + tags: { + // Allow page crashes to be easily searched in Sentry. + ErrorBoundary: 'yes', + }, + }) + .then((eventId) => setSentryEventId(eventId)) + .catch(console.error); + + import('bowser') + .then(({ default: Bowser }) => setBrowser(Bowser.getParser(window.navigator.userAgent))) + .catch(() => {}); + }, []); + + const goHome = () => { + location.href = '/'; + }; + + if (isNetworkError(error)) { + return ( + + + {/* */} + + + + + + + ); + } + + return ( +
+
+ {logoSrc && ( +
+ + + +
+ )} + +
+
+

+ +

+

+ + + + ), + clearCookies: ( + + + + ), + }} + /> +

+ + + {sourceCode.displayName}: + + {' '}{sourceCode.version} + +
+ +
+ {(isProduction) ? ( + (sentryEnabled && sentryEventId) && ( + + ) + ) : ( + <> + {errorText && ( +