Skip to content

Commit 8908680

Browse files
committed
refactor: simplify DevTools component and enhance performance monitoring
- Removed error handling logic and associated state management to streamline the DevTools component. - Introduced a new view system with 'main' and 'settings' views for better organization. - Added initial test data for performance metrics demonstration. - Updated Sentry integration to handle potential unavailability gracefully.
1 parent 9e0509d commit 8908680

File tree

1 file changed

+139
-167
lines changed

1 file changed

+139
-167
lines changed

apps/web/app/components/PerformanceMonitor.tsx

Lines changed: 139 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,59 @@
1+
import * as Sentry from '@sentry/react';
12
import { useEffect, useState } from 'react';
23
import { usePerformanceMonitoring } from '~/lib/performance';
3-
import * as Sentry from '@sentry/react';
44

55
interface DevToolsProps {
66
enabled?: boolean;
77
}
88

9-
type DevToolTab = 'performance' | 'errors' | 'network' | 'info';
9+
type DevToolView = 'main' | 'settings';
1010

1111
export function DevTools({
1212
enabled = process.env.NODE_ENV === 'development',
1313
}: DevToolsProps) {
1414
const [isVisible, setIsVisible] = useState(false);
15-
const [activeTab, setActiveTab] = useState<DevToolTab>('performance');
15+
const [currentView, setCurrentView] = useState<DevToolView>('main');
1616
const [metrics, setMetrics] = useState<any[]>([]);
17-
const [errors, setErrors] = useState<any[]>([]);
1817
const analytics = usePerformanceMonitoring();
1918

2019
useEffect(() => {
21-
if (!enabled || !analytics) return;
20+
if (!enabled) return;
21+
22+
// Add some initial test data for demonstration
23+
setMetrics([
24+
{ name: 'FCP', value: 1200, timestamp: Date.now() - 1000 },
25+
{ name: 'LCP', value: 2100, timestamp: Date.now() - 2000 },
26+
{ name: 'CLS', value: 0.05, timestamp: Date.now() - 3000 },
27+
]);
2228

2329
// Listen for custom performance events
2430
const handlePerformanceEvent = (event: CustomEvent) => {
2531
setMetrics(prev => [event.detail, ...prev].slice(0, 10));
2632
};
2733

28-
// Listen for error events
29-
const handleErrorEvent = (event: CustomEvent) => {
30-
setErrors(prev => [event.detail, ...prev].slice(0, 10));
31-
};
32-
33-
// Listen for global errors
34-
const handleGlobalError = (event: ErrorEvent) => {
35-
setErrors(prev =>
36-
[
37-
{
38-
type: 'JavaScript Error',
39-
message: event.message,
40-
filename: event.filename,
41-
lineno: event.lineno,
42-
colno: event.colno,
43-
timestamp: new Date().toISOString(),
44-
},
45-
...prev,
46-
].slice(0, 10)
47-
);
48-
};
49-
5034
window.addEventListener(
5135
'performance-metric',
5236
handlePerformanceEvent as EventListener
5337
);
54-
window.addEventListener(
55-
'dev-tools-error',
56-
handleErrorEvent as EventListener
57-
);
58-
window.addEventListener('error', handleGlobalError);
5938

60-
// Initialize Sentry breadcrumb (silent)
61-
Sentry.addBreadcrumb({
62-
message: 'Dev Tools initialized',
63-
level: 'info',
64-
data: { component: 'DevTools' },
65-
});
39+
// Initialize Sentry breadcrumb (silent) - only if Sentry is available
40+
try {
41+
Sentry.addBreadcrumb({
42+
message: 'Dev Tools initialized',
43+
level: 'info',
44+
data: { component: 'DevTools' },
45+
});
46+
} catch (error) {
47+
// Sentry not available, continue without it
48+
}
6649

6750
return () => {
6851
window.removeEventListener(
6952
'performance-metric',
7053
handlePerformanceEvent as EventListener
7154
);
72-
window.removeEventListener(
73-
'dev-tools-error',
74-
handleErrorEvent as EventListener
75-
);
76-
window.removeEventListener('error', handleGlobalError);
7755
};
78-
}, [enabled, analytics]);
56+
}, [enabled]);
7957

8058
if (!enabled) return null;
8159

@@ -108,113 +86,123 @@ export function DevTools({
10886
return value > budget ? 'text-red-600' : 'text-green-600';
10987
};
11088

111-
const tabs = [
112-
{ id: 'performance' as const, label: 'Perf', icon: '📊' },
113-
{ id: 'errors' as const, label: 'Errors', icon: '🚨' },
114-
{ id: 'network' as const, label: 'Network', icon: '🌐' },
115-
{ id: 'info' as const, label: 'Info', icon: 'ℹ️' },
116-
];
117-
118-
const renderTabContent = () => {
119-
switch (activeTab) {
120-
case 'performance':
121-
return (
122-
<div className='space-y-3'>
123-
{metrics.length > 0 ? (
124-
<div className='space-y-1 max-h-32 overflow-y-auto'>
125-
{metrics.map((metric, index) => (
126-
<div
127-
key={index}
128-
className='text-xs border-l-2 border-gray-200 pl-2'
129-
>
130-
<div className='flex justify-between items-center'>
131-
<span className='font-mono text-gray-600'>
132-
{metric.name}
133-
</span>
134-
<span
135-
className={`font-semibold ${getStatusColor(metric.name, metric.value)}`}
136-
>
137-
{formatValue(metric.value, metric.name)}
138-
</span>
139-
</div>
140-
</div>
141-
))}
142-
</div>
143-
) : (
144-
<div className='text-gray-500 text-center py-2 text-xs'>
145-
No metrics yet
146-
</div>
147-
)}
148-
</div>
149-
);
150-
151-
case 'errors':
152-
return (
153-
<div className='space-y-3'>
154-
{errors.length > 0 ? (
155-
<div className='space-y-1 max-h-32 overflow-y-auto'>
156-
{errors.map((error, index) => (
157-
<div
158-
key={index}
159-
className='text-xs border-l-2 border-red-200 pl-2'
160-
>
161-
<div className='font-mono text-red-600'>
162-
{error.type || 'Error'}
163-
</div>
164-
<div className='text-gray-500 truncate'>
165-
{error.message}
166-
</div>
167-
{error.filename && (
168-
<div className='text-gray-400 text-xs'>
169-
{error.filename}:{error.lineno}
170-
</div>
171-
)}
172-
</div>
173-
))}
174-
</div>
175-
) : (
176-
<div className='text-gray-500 text-center py-2 text-xs'>
177-
No errors detected
178-
</div>
179-
)}
180-
</div>
181-
);
182-
183-
case 'network':
184-
return (
185-
<div className='space-y-3'>
186-
<div className='text-gray-500 text-center py-2 text-xs'>
187-
Network monitoring coming soon
188-
</div>
89+
const renderMainView = () => (
90+
<div className='space-y-3'>
91+
{/* Web Vitals in a single row */}
92+
<div className='flex items-center space-x-4'>
93+
{metrics.slice(0, 4).map((metric, index) => (
94+
<div key={index} className='flex items-center space-x-1'>
95+
<span className='text-xs text-gray-500 font-mono'>
96+
{metric.name}:
97+
</span>
98+
<span
99+
className={`text-xs font-semibold ${getStatusColor(metric.name, metric.value)}`}
100+
>
101+
{formatValue(metric.value, metric.name)}
102+
</span>
189103
</div>
190-
);
104+
))}
105+
</div>
191106

192-
case 'info':
193-
return (
194-
<div className='space-y-3'>
195-
<div className='space-y-1 text-xs'>
196-
<div className='flex justify-between'>
197-
<span className='text-gray-500'>Environment:</span>
198-
<span className='font-mono'>{import.meta.env.MODE}</span>
199-
</div>
200-
<div className='flex justify-between'>
201-
<span className='text-gray-500'>Sentry:</span>
202-
<span className='font-mono'>
203-
{import.meta.env.VITE_SENTRY_DSN ? 'Enabled' : 'Disabled'}
204-
</span>
205-
</div>
206-
<div className='flex justify-between'>
207-
<span className='text-gray-500'>React Router:</span>
208-
<span className='font-mono'>v7</span>
209-
</div>
210-
</div>
211-
</div>
212-
);
107+
{/* Settings button on its own row */}
108+
<button
109+
onClick={() => setCurrentView('settings')}
110+
className='w-full flex items-center justify-center space-x-2 px-3 py-2 text-xs bg-gray-100 text-gray-700 rounded hover:bg-gray-200 transition-colors'
111+
>
112+
<svg
113+
className='w-4 h-4'
114+
fill='none'
115+
stroke='currentColor'
116+
viewBox='0 0 24 24'
117+
>
118+
<path
119+
strokeLinecap='round'
120+
strokeLinejoin='round'
121+
strokeWidth={2}
122+
d='M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z'
123+
/>
124+
<path
125+
strokeLinecap='round'
126+
strokeLinejoin='round'
127+
strokeWidth={2}
128+
d='M15 12a3 3 0 11-6 0 3 3 0 016 0z'
129+
/>
130+
</svg>
131+
<span>Settings</span>
132+
</button>
133+
</div>
134+
);
213135

214-
default:
215-
return null;
216-
}
217-
};
136+
const renderSettingsView = () => (
137+
<div className='space-y-3'>
138+
<div className='flex items-center space-x-2'>
139+
<button
140+
onClick={() => setCurrentView('main')}
141+
className='text-gray-500 hover:text-gray-700'
142+
>
143+
<svg
144+
className='w-4 h-4'
145+
fill='none'
146+
stroke='currentColor'
147+
viewBox='0 0 24 24'
148+
>
149+
<path
150+
strokeLinecap='round'
151+
strokeLinejoin='round'
152+
strokeWidth={2}
153+
d='M15 19l-7-7 7-7'
154+
/>
155+
</svg>
156+
</button>
157+
<h3 className='font-semibold text-gray-800 text-sm'>Settings</h3>
158+
</div>
159+
160+
<div className='space-y-2 text-xs'>
161+
<div className='flex justify-between items-center'>
162+
<span className='text-gray-600'>Current Route:</span>
163+
<span className='font-mono text-gray-800'>
164+
{window.location.pathname}
165+
</span>
166+
</div>
167+
<div className='flex justify-between items-center'>
168+
<span className='text-gray-600'>React Router:</span>
169+
<span className='font-mono text-gray-800'>v7</span>
170+
</div>
171+
<div className='flex justify-between items-center'>
172+
<span className='text-gray-600'>Environment:</span>
173+
<span className='font-mono text-gray-800'>
174+
{import.meta.env.MODE}
175+
</span>
176+
</div>
177+
</div>
178+
179+
<div className='pt-2 border-t border-gray-200 space-y-2'>
180+
<button
181+
onClick={() => {
182+
// Restart dev server functionality
183+
window.location.reload();
184+
}}
185+
className='w-full px-3 py-2 text-xs bg-blue-100 text-blue-700 rounded hover:bg-blue-200 transition-colors'
186+
>
187+
Restart Dev Server
188+
</button>
189+
<button
190+
onClick={() => {
191+
// Clear cache functionality
192+
if ('caches' in window) {
193+
caches.keys().then(names => {
194+
names.forEach(name => caches.delete(name));
195+
});
196+
}
197+
window.location.reload();
198+
}}
199+
className='w-full px-3 py-2 text-xs bg-orange-100 text-orange-700 rounded hover:bg-orange-200 transition-colors'
200+
>
201+
Clear Cache & Reload
202+
</button>
203+
</div>
204+
</div>
205+
);
218206

219207
return (
220208
<>
@@ -253,26 +241,10 @@ export function DevTools({
253241
</button>
254242
</div>
255243

256-
{/* Tabs */}
257-
<div className='flex border-b border-gray-200'>
258-
{tabs.map(tab => (
259-
<button
260-
key={tab.id}
261-
onClick={() => setActiveTab(tab.id)}
262-
className={`flex-1 px-2 py-1 text-xs font-medium transition-colors ${
263-
activeTab === tab.id
264-
? 'bg-blue-50 text-blue-600 border-b-2 border-blue-600'
265-
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-50'
266-
}`}
267-
>
268-
<span className='mr-1'>{tab.icon}</span>
269-
{tab.label}
270-
</button>
271-
))}
272-
</div>
273-
274244
{/* Content */}
275-
<div className='p-3'>{renderTabContent()}</div>
245+
<div className='p-3'>
246+
{currentView === 'main' ? renderMainView() : renderSettingsView()}
247+
</div>
276248
</div>
277249
)}
278250
</>

0 commit comments

Comments
 (0)