Skip to content

Commit 9e0509d

Browse files
committed
refactor: replace @sentry/nextjs with @sentry/react and update performance monitoring
1 parent 3ed5e57 commit 9e0509d

File tree

10 files changed

+315
-485
lines changed

10 files changed

+315
-485
lines changed
Lines changed: 183 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
import { Analytics } from '@vercel/analytics/react';
21
import { useEffect, useState } from 'react';
32
import { usePerformanceMonitoring } from '~/lib/performance';
3+
import * as Sentry from '@sentry/react';
44

5-
interface PerformanceMonitorProps {
5+
interface DevToolsProps {
66
enabled?: boolean;
77
}
88

9-
export function PerformanceMonitor({
9+
type DevToolTab = 'performance' | 'errors' | 'network' | 'info';
10+
11+
export function DevTools({
1012
enabled = process.env.NODE_ENV === 'development',
11-
}: PerformanceMonitorProps) {
13+
}: DevToolsProps) {
1214
const [isVisible, setIsVisible] = useState(false);
15+
const [activeTab, setActiveTab] = useState<DevToolTab>('performance');
1316
const [metrics, setMetrics] = useState<any[]>([]);
17+
const [errors, setErrors] = useState<any[]>([]);
1418
const analytics = usePerformanceMonitoring();
1519

1620
useEffect(() => {
@@ -21,16 +25,55 @@ export function PerformanceMonitor({
2125
setMetrics(prev => [event.detail, ...prev].slice(0, 10));
2226
};
2327

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+
2450
window.addEventListener(
2551
'performance-metric',
2652
handlePerformanceEvent as EventListener
2753
);
54+
window.addEventListener(
55+
'dev-tools-error',
56+
handleErrorEvent as EventListener
57+
);
58+
window.addEventListener('error', handleGlobalError);
59+
60+
// Initialize Sentry breadcrumb (silent)
61+
Sentry.addBreadcrumb({
62+
message: 'Dev Tools initialized',
63+
level: 'info',
64+
data: { component: 'DevTools' },
65+
});
2866

2967
return () => {
3068
window.removeEventListener(
3169
'performance-metric',
3270
handlePerformanceEvent as EventListener
3371
);
72+
window.removeEventListener(
73+
'dev-tools-error',
74+
handleErrorEvent as EventListener
75+
);
76+
window.removeEventListener('error', handleGlobalError);
3477
};
3578
}, [enabled, analytics]);
3679

@@ -65,40 +108,137 @@ export function PerformanceMonitor({
65108
return value > budget ? 'text-red-600' : 'text-green-600';
66109
};
67110

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>
189+
</div>
190+
);
191+
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+
);
213+
214+
default:
215+
return null;
216+
}
217+
};
218+
68219
return (
69220
<>
70221
{/* Toggle Button */}
71222
<button
72223
onClick={() => setIsVisible(!isVisible)}
73-
className='fixed bottom-4 right-4 z-50 bg-blue-600 text-white p-3 rounded-full shadow-lg hover:bg-blue-700 transition-colors'
74-
title='Performance Monitor'
224+
className='fixed bottom-4 left-4 z-50 bg-gray-300 text-gray-700 p-2 rounded-lg shadow-lg hover:bg-gray-400 transition-colors'
225+
title='Development Tools'
75226
>
76-
<svg
77-
className='w-6 h-6'
78-
fill='none'
79-
stroke='currentColor'
80-
viewBox='0 0 24 24'
81-
>
82-
<path
83-
strokeLinecap='round'
84-
strokeLinejoin='round'
85-
strokeWidth={2}
86-
d='M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z'
87-
/>
88-
</svg>
227+
<img src='/stratos-icon.png' alt='Stratos' className='w-4 h-4' />
89228
</button>
90229

91-
{/* Monitor Panel */}
230+
{/* Dev Tools Panel */}
92231
{isVisible && (
93-
<div className='fixed bottom-20 right-4 z-50 bg-white border border-gray-200 rounded-lg shadow-xl w-96 max-h-96 overflow-hidden'>
94-
<div className='bg-gray-50 px-4 py-2 border-b border-gray-200 flex justify-between items-center'>
95-
<h3 className='font-semibold text-gray-800'>Performance Monitor</h3>
232+
<div className='fixed bottom-16 left-4 z-50 bg-white border border-gray-200 rounded-lg shadow-xl w-80 max-h-80 overflow-hidden'>
233+
{/* Header */}
234+
<div className='bg-gray-50 px-3 py-2 border-b border-gray-200 flex justify-between items-center'>
235+
<h3 className='font-semibold text-gray-800 text-sm'>Dev Tools</h3>
96236
<button
97237
onClick={() => setIsVisible(false)}
98238
className='text-gray-500 hover:text-gray-700'
99239
>
100240
<svg
101-
className='w-4 h-4'
241+
className='w-3 h-3'
102242
fill='none'
103243
stroke='currentColor'
104244
viewBox='0 0 24 24'
@@ -113,55 +253,28 @@ export function PerformanceMonitor({
113253
</button>
114254
</div>
115255

116-
<div className='p-4 space-y-4'>
117-
{/* Web Vitals Info */}
118-
<div className='text-sm text-gray-600'>
119-
<p>Web Vitals are automatically tracked by Vercel Analytics.</p>
120-
<p>Check your Vercel dashboard for detailed metrics.</p>
121-
</div>
122-
123-
{/* Recent Metrics */}
124-
{metrics.length > 0 && (
125-
<div>
126-
<h4 className='text-sm font-medium text-gray-700 mb-2'>
127-
Recent Metrics
128-
</h4>
129-
<div className='space-y-2 max-h-48 overflow-y-auto'>
130-
{metrics.map((metric, index) => (
131-
<div
132-
key={index}
133-
className='text-xs border-l-2 border-gray-200 pl-2'
134-
>
135-
<div className='flex justify-between items-center'>
136-
<span className='font-mono text-gray-600'>
137-
{metric.name}
138-
</span>
139-
<span
140-
className={`font-semibold ${getStatusColor(metric.name, metric.value)}`}
141-
>
142-
{formatValue(metric.value, metric.name)}
143-
</span>
144-
</div>
145-
</div>
146-
))}
147-
</div>
148-
</div>
149-
)}
150-
151-
{metrics.length === 0 && (
152-
<div className='text-gray-500 text-center py-4'>
153-
<p>No custom metrics yet</p>
154-
<p className='text-xs mt-1'>
155-
Web Vitals are tracked automatically
156-
</p>
157-
</div>
158-
)}
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+
))}
159272
</div>
273+
274+
{/* Content */}
275+
<div className='p-3'>{renderTabContent()}</div>
160276
</div>
161277
)}
162-
163-
{/* Vercel Analytics */}
164-
<Analytics />
165278
</>
166279
);
167280
}

apps/web/app/lib/envValidation.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ const envSchema = z.object({
4242
// Logging
4343
LOG_LEVEL: z.enum(['error', 'warn', 'info', 'http', 'debug']).default('info'),
4444

45+
// Sentry (optional)
46+
SENTRY_DSN: z.string().url().optional(),
47+
4548
// File uploads
4649
MAX_FILE_SIZE: z.string().default('10MB'),
4750
ALLOWED_FILE_TYPES: z

0 commit comments

Comments
 (0)