Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added diagnostic/build-68fe2391-part001.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part002.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part003.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part004.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part005.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part006.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part007.logd
Binary file not shown.
Binary file added diagnostic/build-68fe2391-part008.logd
Binary file not shown.
96 changes: 96 additions & 0 deletions diagnostic/build-68fe2391.json

Large diffs are not rendered by default.

98 changes: 98 additions & 0 deletions frontend/src/components/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';

interface ErrorBoundaryProps {
children: React.ReactNode;
title?: string;
message?: string;
className?: string;
}

interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}

const fallbackStyles: React.CSSProperties = {
backgroundColor: '#1e293b',
border: '1px solid #334155',
borderRadius: 12,
color: '#cbd5e1',
padding: '1rem',
width: '100%',
};

const titleStyles: React.CSSProperties = {
color: '#f8fafc',
fontSize: '1rem',
fontWeight: 600,
margin: '0 0 0.25rem',
};

const messageStyles: React.CSSProperties = {
color: '#94a3b8',
fontSize: '0.875rem',
margin: 0,
};

const buttonStyles: React.CSSProperties = {
backgroundColor: '#334155',
border: '1px solid #475569',
borderRadius: 8,
color: '#e2e8f0',
cursor: 'pointer',
font: 'inherit',
fontSize: '0.875rem',
marginTop: '0.75rem',
padding: '0.5rem 0.75rem',
};

class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = {
hasError: false,
error: null,
};

static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return {
hasError: true,
error,
};
}

componentDidCatch(error: Error, info: React.ErrorInfo) {
console.error('UI section render failed', error, info);
}

reset = () => {
this.setState({
hasError: false,
error: null,
});
};

render() {
if (!this.state.hasError) {
return this.props.children;
}

const title = this.props.title || 'Section unavailable';
const message = this.props.message || 'This area failed to render. The rest of the dashboard is still available.';

return (
<div
className={this.props.className || 'error-boundary-fallback'}
role="alert"
aria-live="polite"
style={fallbackStyles}
>
<h3 style={titleStyles}>{title}</h3>
<p style={messageStyles}>{message}</p>
<button type="button" style={buttonStyles} onClick={this.reset}>
Try again
</button>
</div>
);
}
}

export default ErrorBoundary;
26 changes: 21 additions & 5 deletions frontend/src/components/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import ErrorBoundary from './ErrorBoundary';
import Header from './Header';
import Sidebar from './Sidebar';

Expand All @@ -11,13 +12,28 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {

return (
<div className="app-layout">
<Header
onMenuToggle={() => setSidebarOpen((prev) => !prev)}
/>
<ErrorBoundary
title="Header unavailable"
message="The header could not be rendered. Navigation remains available below."
>
<Header
onMenuToggle={() => setSidebarOpen((prev) => !prev)}
/>
</ErrorBoundary>
<div className="app-body">
<Sidebar isOpen={sidebarOpen} />
<ErrorBoundary
title="Sidebar unavailable"
message="The sidebar failed to load. Main content is still available."
>
<Sidebar isOpen={sidebarOpen} />
</ErrorBoundary>
<main className="app-content">
{children}
<ErrorBoundary
title="Content unavailable"
message="This page failed to render, but the application shell is still running."
>
{children}
</ErrorBoundary>
</main>
</div>
</div>
Expand Down
107 changes: 68 additions & 39 deletions frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import React from 'react';
import ErrorBoundary from '../components/ErrorBoundary';
import { useDashboardStats } from '../hooks';
import type { DashboardStats } from '../types';

const statCards = [
type StatKey = keyof Omit<DashboardStats, 'metrics'>;

interface StatCardConfig {
key: StatKey;
label: string;
color: string;
suffix?: string;
}

const statCards: StatCardConfig[] = [
{ key: 'totalUsers', label: 'Total Users', color: '#4f46e5' },
{ key: 'activeSessions', label: 'Active Sessions', color: '#059669' },
{ key: 'trialsCompleted', label: 'Trials Completed', color: '#d97706' },
Expand All @@ -10,6 +21,33 @@ const statCards = [
{ key: 'uptime', label: 'Uptime', color: '#0891b2', suffix: '%' },
];

interface StatCardsGridProps {
stats: DashboardStats | undefined;
}

const StatCardsGrid: React.FC<StatCardsGridProps> = ({ stats }) => (
<div className="stats-grid">
{statCards.map((card) => (
<div key={card.key} className="stat-card">
<div
className="stat-card-indicator"
style={{ backgroundColor: card.color }}
/>
<div className="stat-card-content">
<span className="stat-card-label">{card.label}</span>
<span
className="stat-card-value"
style={{ color: card.color }}
>
{String(stats?.[card.key] ?? ' - ')}
{card.suffix || ''}
</span>
</div>
</div>
))}
</div>
);

const Dashboard: React.FC = () => {
const { data: stats, isLoading, error } = useDashboardStats();

Expand All @@ -31,50 +69,41 @@ const Dashboard: React.FC = () => {
}

return (
<div className="dashboard">
<div className="dashboard-header">
<h2>Dashboard</h2>
<p className="dashboard-subtitle">
Tent of Trials System Overview
</p>
</div>
<ErrorBoundary
title="Dashboard unavailable"
message="The dashboard panel failed to render. Other application sections remain available."
>
<div className="dashboard">
<div className="dashboard-header">
<h2>Dashboard</h2>
<p className="dashboard-subtitle">
Tent of Trials System Overview
</p>
</div>

<div className="stats-grid">
{statCards.map((card) => (
<div key={card.key} className="stat-card">
<div
className="stat-card-indicator"
style={{ backgroundColor: card.color }}
/>
<div className="stat-card-content">
<span className="stat-card-label">{card.label}</span>
<span
className="stat-card-value"
style={{ color: card.color }}
>
{String((stats as any)?.[card.key] ?? ' - ')}
{card.suffix || ''}
</span>
</div>
</div>
))}
</div>
<ErrorBoundary
title="Dashboard stats unavailable"
message="Live stat cards failed to render. Static dashboard panels remain available."
>
<StatCardsGrid stats={stats} />
</ErrorBoundary>

<div className="dashboard-panels">
<div className="panel">
<h3>Recent Activity</h3>
<div className="panel-placeholder">
Activity feed will appear here
<div className="dashboard-panels">
<div className="panel">
<h3>Recent Activity</h3>
<div className="panel-placeholder">
Activity feed will appear here
</div>
</div>
</div>
<div className="panel">
<h3>System Health</h3>
<div className="panel-placeholder">
Health metrics will appear here
<div className="panel">
<h3>System Health</h3>
<div className="panel-placeholder">
Health metrics will appear here
</div>
</div>
</div>
</div>
</div>
</ErrorBoundary>
);
};

Expand Down