diff --git a/diagnostic/build-2b707695-part001.logd b/diagnostic/build-2b707695-part001.logd new file mode 100644 index 00000000..cdf18694 Binary files /dev/null and b/diagnostic/build-2b707695-part001.logd differ diff --git a/diagnostic/build-2b707695-part002.logd b/diagnostic/build-2b707695-part002.logd new file mode 100644 index 00000000..a403aedc Binary files /dev/null and b/diagnostic/build-2b707695-part002.logd differ diff --git a/diagnostic/build-2b707695-part003.logd b/diagnostic/build-2b707695-part003.logd new file mode 100644 index 00000000..72d590a0 Binary files /dev/null and b/diagnostic/build-2b707695-part003.logd differ diff --git a/diagnostic/build-2b707695-part004.logd b/diagnostic/build-2b707695-part004.logd new file mode 100644 index 00000000..3f3c790f Binary files /dev/null and b/diagnostic/build-2b707695-part004.logd differ diff --git a/diagnostic/build-2b707695.json b/diagnostic/build-2b707695.json new file mode 100644 index 00000000..4fcbbd68 --- /dev/null +++ b/diagnostic/build-2b707695.json @@ -0,0 +1,29 @@ +{ + "generated_at": "2026-06-23T20:40:04.002065+00:00", + "commit": "2b707695", + "diagnostic_logd": [ + "diagnostic\\build-2b707695-part001.logd", + "diagnostic\\build-2b707695-part002.logd", + "diagnostic\\build-2b707695-part003.logd", + "diagnostic\\build-2b707695-part004.logd" + ], + "diagnostic_logd_error": null, + "message_blocker": null, + "chunked": true, + "chunk_size_bytes": 41943040, + "password": "abd282865073fcbdd0f7", + "decrypt_command": "encryptly unpack diagnostic\\build-2b707695.logd --password abd282865073fcbdd0f7", + "total_modules": 1, + "passed": 1, + "failed": 0, + "modules": [ + { + "name": "frontend", + "status": "PASS", + "elapsed_seconds": 4.772, + "artifact": "D:\\\u6211\u7684\u575a\u679c\u4e91\\\u4e2a\u4eba\\SODA\u8ba1\u5212\\github-bounty-work\\tentoftrials-error-boundaries\\frontend\\dist", + "output": "" + } + ], + "pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic\\build-2b707695-part001.logd, diagnostic\\build-2b707695-part002.logd, diagnostic\\build-2b707695-part003.logd, diagnostic\\build-2b707695-part004.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging." +} diff --git a/frontend/src/components/ErrorBoundary.tsx b/frontend/src/components/ErrorBoundary.tsx new file mode 100644 index 00000000..c71804f2 --- /dev/null +++ b/frontend/src/components/ErrorBoundary.tsx @@ -0,0 +1,64 @@ +import React from 'react'; + +interface ErrorBoundaryProps { + children: React.ReactNode; + fallbackTitle?: string; + fallbackMessage?: string; +} + +interface ErrorBoundaryState { + hasError: boolean; +} + +const fallbackStyle: React.CSSProperties = { + border: '1px solid rgba(220, 38, 38, 0.28)', + borderRadius: 12, + background: 'rgba(254, 242, 242, 0.96)', + color: '#7f1d1d', + padding: '1rem', +}; + +const fallbackTitleStyle: React.CSSProperties = { + margin: '0 0 0.35rem', + fontSize: '1rem', + fontWeight: 700, +}; + +const fallbackMessageStyle: React.CSSProperties = { + margin: 0, + color: '#991b1b', +}; + +class ErrorBoundary extends React.Component { + state: ErrorBoundaryState = { + hasError: false, + }; + + static getDerivedStateFromError(): ErrorBoundaryState { + return { hasError: true }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Dashboard render error boundary caught an error', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( +
+

+ {this.props.fallbackTitle ?? 'This section could not be displayed'} +

+

+ {this.props.fallbackMessage ?? + 'The rest of the dashboard is still available. Refresh the page or try again shortly.'} +

+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 447dcfa3..7c777874 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import ErrorBoundary from '../components/ErrorBoundary'; import { useDashboardStats } from '../hooks'; const statCards = [ @@ -10,7 +11,34 @@ const statCards = [ { key: 'uptime', label: 'Uptime', color: '#0891b2', suffix: '%' }, ]; -const Dashboard: React.FC = () => { +interface StatsGridProps { + stats: Record | undefined; +} + +const StatsGrid: React.FC = ({ stats }) => ( +
+ {statCards.map((card) => ( +
+
+
+ {card.label} + + {String(stats?.[card.key] ?? ' - ')} + {card.suffix || ''} + +
+
+ ))} +
+); + +const DashboardContent: React.FC = () => { const { data: stats, isLoading, error } = useDashboardStats(); if (isLoading) { @@ -39,26 +67,12 @@ const Dashboard: React.FC = () => {

-
- {statCards.map((card) => ( -
-
-
- {card.label} - - {String((stats as any)?.[card.key] ?? ' - ')} - {card.suffix || ''} - -
-
- ))} -
+ + | undefined} /> +
@@ -78,4 +92,13 @@ const Dashboard: React.FC = () => { ); }; +const Dashboard: React.FC = () => ( + + + +); + export default Dashboard;