🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Monitoreo de Frontend LogRocket
Ejemplos de LogRocket para reproducción de sesiones, seguimiento de errores, monitoreo de rendimiento y análisis de comportamiento de usuario
💻 LogRocket Hello World typescript
🟢 simple
⭐
Configuración básica de LogRocket con grabación de sesión e inicialización
⏱️ 10 min
🏷️ frontend, monitoring, analytics
Prerequisites:
JavaScript basics, HTML/CSS, Browser APIs
// LogRocket Hello World - Basic Frontend Monitoring
// Simple session recording and error tracking setup
import LogRocket from 'logrocket'
// Initialize LogRocket with your application ID
LogRocket.init('your-app-id')
// Record basic user information
LogRocket.identify('user-123', {
name: 'John Doe',
email: '[email protected]',
subscriptionType: 'premium'
})
// Track custom events
LogRocket.track('User signed up', {
method: 'email',
timestamp: new Date().toISOString()
})
// Console.log for debugging
console.log('LogRocket initialized and recording!')
// Handle errors automatically
window.addEventListener('error', (event) => {
LogRocket.captureException(event.error)
})
// Track page navigation
window.addEventListener('popstate', () => {
LogRocket.track('Page navigation', {
url: window.location.href,
timestamp: new Date().toISOString()
})
})
💻 Reproducción Avanzada de Sesión typescript
🟡 intermediate
⭐⭐⭐
Grabación de sesión configurable con eventos personalizados y filtros
⏱️ 25 min
🏷️ frontend, monitoring, analytics, performance
Prerequisites:
LogRocket basics, DOM manipulation, Performance APIs, Event handling
// LogRocket Advanced Session Replay
// Comprehensive session recording with custom event tracking
import LogRocket from 'logrocket'
import LogRocketFuzzySearch from 'logrocket-fuzzy-search'
// Initialize with advanced configuration
LogRocket.init('your-app-id', {
network: {
requestSanitizer: (request) => {
// Remove sensitive headers
if (request.headers.authorization) {
delete request.headers.authorization
}
if (request.body && request.body.password) {
request.body.password = '***'
}
return request
},
responseSanitizer: (response) => {
// Sanitize sensitive response data
if (response.body && response.body.token) {
response.body.token = '***'
}
return response
}
},
console: {
shouldAggregateConsoleLogs: true
},
dom: {
baseHref: window.location.origin,
inputSanitizer: true
}
})
// Enable fuzzy search for text inputs
LogRocketFuzzySearch(LogRocket)
// Track form interactions
class FormTracker {
constructor() {
this.setupFormListeners()
}
private setupFormListeners() {
// Track form submissions
document.addEventListener('submit', (event) => {
const form = event.target as HTMLFormElement
LogRocket.track('Form submitted', {
formId: form.id,
formName: form.name,
fields: this.getFormData(form)
})
})
// Track field focus events
document.addEventListener('focusin', (event) => {
const element = event.target as HTMLElement
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
LogRocket.track('Field focused', {
fieldName: element.getAttribute('name'),
fieldType: element.getAttribute('type'),
fieldId: element.id
})
}
})
// Track validation errors
document.addEventListener('invalid', (event) => {
const element = event.target as HTMLInputElement
LogRocket.track('Validation error', {
fieldName: element.name,
fieldType: element.type,
validationMessage: element.validationMessage
})
})
}
private getFormData(form: HTMLFormElement): Record<string, any> {
const formData = new FormData(form)
const data: Record<string, any> = {}
// Sanitize sensitive data
for (const [key, value] of formData.entries()) {
if (key.toLowerCase().includes('password') ||
key.toLowerCase().includes('token') ||
key.toLowerCase().includes('secret')) {
data[key] = '***'
} else {
data[key] = value
}
}
return data
}
}
// Track user interactions with specific elements
class InteractionTracker {
constructor() {
this.setupClickTracking()
this.setupScrollTracking()
this.setupRageClickDetection()
}
private setupClickTracking() {
document.addEventListener('click', (event) => {
const element = event.target as HTMLElement
const elementInfo = {
tagName: element.tagName,
className: element.className,
id: element.id,
textContent: element.textContent?.slice(0, 50),
attributes: this.getElementAttributes(element)
}
LogRocket.track('Element clicked', elementInfo)
})
}
private setupScrollTracking() {
let lastScrollTop = 0
let scrollTimeout: number
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout)
scrollTimeout = window.setTimeout(() => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
const scrollDirection = scrollTop > lastScrollTop ? 'down' : 'up'
const scrollPercentage = Math.round(
(scrollTop / (document.documentElement.scrollHeight - window.innerHeight)) * 100
)
LogRocket.track('Page scrolled', {
direction: scrollDirection,
percentage: scrollPercentage,
scrollTop
})
lastScrollTop = scrollTop
}, 100)
})
}
private setupRageClickDetection() {
const clicks: Array<{ time: number; x: number; y: number }> = []
const RAGE_CLICK_THRESHOLD = 3
const RAGE_CLICK_TIMEFRAME = 1000 // 1 second
document.addEventListener('click', (event) => {
const now = Date.now()
const click = { time: now, x: event.clientX, y: event.clientY }
// Remove old clicks
clicks.filter(c => now - c.time < RAGE_CLICK_TIMEFRAME)
clicks.push(click)
if (clicks.length >= RAGE_CLICK_THRESHOLD) {
LogRocket.track('Rage click detected', {
clickCount: clicks.length,
timeframe: RAGE_CLICK_TIMEFRAME,
x: event.clientX,
y: event.clientY,
target: (event.target as HTMLElement).tagName
})
}
})
}
private getElementAttributes(element: HTMLElement): Record<string, string> {
const attrs: Record<string, string> = {}
for (const attr of element.attributes) {
if (!['id', 'class', 'style'].includes(attr.name)) {
attrs[attr.name] = attr.value
}
}
return attrs
}
}
// Initialize trackers
const formTracker = new FormTracker()
const interactionTracker = new InteractionTracker()
// Track performance metrics
class PerformanceTracker {
constructor() {
this.trackPageLoad()
this.trackResourceTiming()
}
private trackPageLoad() {
window.addEventListener('load', () => {
const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
LogRocket.track('Page load completed', {
loadTime: navigation.loadEventEnd - navigation.fetchStart,
domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
firstPaint: this.getFirstPaint(),
firstContentfulPaint: this.getFirstContentfulPaint()
})
})
}
private trackResourceTiming() {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'resource') {
LogRocket.track('Resource loaded', {
name: entry.name,
duration: entry.duration,
size: (entry as PerformanceResourceTiming).transferSize,
type: this.getResourceType(entry.name)
})
}
}
})
observer.observe({ entryTypes: ['resource'] })
}
private getFirstPaint(): number {
const paintEntries = performance.getEntriesByType('paint')
const firstPaint = paintEntries.find(entry => entry.name === 'first-paint')
return firstPaint ? firstPaint.startTime : 0
}
private getFirstContentfulPaint(): number {
const paintEntries = performance.getEntriesByType('paint')
const fcp = paintEntries.find(entry => entry.name === 'first-contentful-paint')
return fcp ? fcp.startTime : 0
}
private getResourceType(url: string): string {
if (url.match(/\.(css)$/)) return 'css'
if (url.match(/\.(js)$/)) return 'javascript'
if (url.match(/\.(png|jpg|jpeg|gif|webp|svg)$/)) return 'image'
if (url.match(/\.(woff|woff2|ttf|eot)$/)) return 'font'
return 'other'
}
}
// Initialize performance tracker
const performanceTracker = new PerformanceTracker()
console.log('LogRocket advanced session replay initialized')
// Export for React usage
export { FormTracker, InteractionTracker, PerformanceTracker }
💻 Integración con React Error Boundary typescript
🟡 intermediate
⭐⭐⭐
React error boundary con integración LogRocket para seguimiento completo de errores
⏱️ 20 min
🏷️ react, frontend, error-handling, monitoring
Prerequisites:
React basics, JavaScript error handling, LogRocket
// LogRocket React Error Boundary Integration
// Automatic error capturing and reporting in React applications
import React, { Component, ErrorInfo, ReactNode } from 'react'
import LogRocket from 'logrocket'
interface Props {
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, errorInfo: ErrorInfo) => void
}
interface State {
hasError: boolean
error?: Error
errorInfo?: ErrorInfo
retryCount: number
}
class LogRocketErrorBoundary extends Component<Props, State> {
private maxRetries = 3
constructor(props: Props) {
super(props)
this.state = {
hasError: false,
retryCount: 0
}
}
static getDerivedStateFromError(error: Error): Partial<State> {
return {
hasError: true,
error
}
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({ errorInfo })
// Log to LogRocket
LogRocket.captureException(error, {
tags: {
component: 'ErrorBoundary',
severity: 'critical'
},
extra: {
errorInfo,
componentStack: errorInfo.componentStack,
retryCount: this.state.retryCount
}
})
// Track error event
LogRocket.track('React error caught', {
errorMessage: error.message,
errorName: error.name,
componentStack: errorInfo.componentStack,
retryCount: this.state.retryCount,
userAgent: navigator.userAgent,
url: window.location.href
})
// Call custom error handler
if (this.props.onError) {
this.props.onError(error, errorInfo)
}
}
handleRetry = () => {
if (this.state.retryCount < this.maxRetries) {
this.setState(prevState => ({
hasError: false,
error: undefined,
errorInfo: undefined,
retryCount: prevState.retryCount + 1
}))
LogRocket.track('Error boundary retry', {
retryCount: this.state.retryCount + 1,
maxRetries: this.maxRetries
})
}
}
render() {
if (this.state.hasError) {
return this.props.fallback || (
<div style={{
padding: '20px',
border: '1px solid #ff6b6b',
borderRadius: '8px',
backgroundColor: '#ffe0e0',
margin: '20px'
}}>
<h2 style={{ color: '#d63031' }}>Something went wrong</h2>
<p>We're sorry, but something unexpected happened.</p>
{this.state.retryCount < this.maxRetries ? (
<button
onClick={this.handleRetry}
style={{
padding: '10px 20px',
backgroundColor: '#0984e3',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
marginRight: '10px'
}}
>
Try Again ({this.maxRetries - this.state.retryCount} attempts left)
</button>
) : (
<p>Please refresh the page or contact support if the problem persists.</p>
)}
<details style={{ marginTop: '20px' }}>
<summary>Error Details</summary>
<pre style={{
fontSize: '12px',
backgroundColor: '#f8f9fa',
padding: '10px',
overflow: 'auto',
maxHeight: '200px'
}}>
{this.state.error?.stack}
</pre>
</details>
</div>
)
}
return this.props.children
}
}
// Hook for functional components
function useLogRocketErrorBoundary() {
const [error, setError] = React.useState<Error | null>(null)
const captureError = React.useCallback((error: Error) => {
setError(error)
LogRocket.captureException(error, {
tags: {
component: 'useLogRocketErrorBoundary',
severity: 'critical'
}
})
LogRocket.track('Functional component error', {
errorMessage: error.message,
errorName: error.name,
stack: error.stack,
url: window.location.href
})
}, [])
const resetError = React.useCallback(() => {
setError(null)
LogRocket.track('Error boundary reset')
}, [])
React.useEffect(() => {
const handleError = (event: ErrorEvent) => {
captureError(event.error)
}
const handleUnhandledRejection = (event: PromiseRejectionEvent) => {
captureError(new Error(event.reason))
}
window.addEventListener('error', handleError)
window.addEventListener('unhandledrejection', handleUnhandledRejection)
return () => {
window.removeEventListener('error', handleError)
window.removeEventListener('unhandledrejection', handleUnhandledRejection)
}
}, [captureError])
return { error, captureError, resetError }
}
// Higher-order component for automatic error boundary
function withLogRocketErrorBoundary<P extends object>(
Component: React.ComponentType<P>,
options?: {
fallback?: ReactNode
onError?: (error: Error, errorInfo: ErrorInfo) => void
}
) {
return function WithErrorBoundary(props: P) {
return (
<LogRocketErrorBoundary
fallback={options?.fallback}
onError={options?.onError}
>
<Component {...props} />
</LogRocketErrorBoundary>
)
}
}
// Usage example
const MyComponent = () => {
const [shouldThrow, setShouldThrow] = React.useState(false)
if (shouldThrow) {
throw new Error('This is a test error!')
}
return (
<div>
<h1>My Component</h1>
<button onClick={() => setShouldThrow(true)}>
Throw Error
</button>
</div>
)
}
// Wrap component with error boundary
const SafeComponent = withLogRocketErrorBoundary(MyComponent, {
onError: (error, errorInfo) => {
console.error('Custom error handler:', error, errorInfo)
}
})
// Functional component with hook
const FunctionalComponent = () => {
const { error, captureError, resetError } = useLogRocketErrorBoundary()
const handleClick = () => {
try {
throw new Error('User triggered error!')
} catch (err) {
captureError(err as Error)
}
}
if (error) {
return (
<div>
<p>Error occurred: {error.message}</p>
<button onClick={resetError}>Reset Error</button>
</div>
)
}
return <button onClick={handleClick}>Trigger Error</button>
}
export {
LogRocketErrorBoundary,
useLogRocketErrorBoundary,
withLogRocketErrorBoundary,
SafeComponent,
FunctionalComponent
}
💻 Panel de Análisis Personalizado typescript
🔴 complex
⭐⭐⭐⭐
Construir un panel de análisis personalizado usando datos e insights de LogRocket
⏱️ 45 min
🏷️ frontend, analytics, dashboard, monitoring
Prerequisites:
React hooks, LogRocket API, Data visualization, State management
// LogRocket Custom Analytics Dashboard
// Real-time analytics dashboard using LogRocket session data
import React, { useState, useEffect, useCallback } from 'react'
import LogRocket from 'logrocket'
interface SessionData {
id: string
timestamp: number
duration: number
userId: string
events: Array<{
type: string
timestamp: number
data: any
}>
errors: Array<{
message: string
stack: string
timestamp: number
}>
pageViews: Array<{
url: string
timestamp: number
duration: number
}>
}
interface AnalyticsMetrics {
totalSessions: number
averageSessionDuration: number
errorRate: number
topPages: Array<{ url: string; views: number }>
commonErrors: Array<{ message: string; count: number }>
userRetention: {
day1: number
day7: number
day30: number
}
}
const LogRocketAnalyticsDashboard: React.FC = () => {
const [sessions, setSessions] = useState<SessionData[]>([])
const [metrics, setMetrics] = useState<AnalyticsMetrics | null>(null)
const [loading, setLoading] = useState(true)
const [dateRange, setDateRange] = useState({
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // 7 days ago
end: new Date()
})
const [selectedSession, setSelectedSession] = useState<SessionData | null>(null)
// Initialize LogRocket session tracking
useEffect(() => {
LogRocket.getSessionURL((sessionURL) => {
if (sessionURL) {
LogRocket.track('Analytics dashboard viewed', {
sessionURL,
timestamp: Date.now()
})
}
})
}, [])
// Fetch session data from LogRocket API
const fetchSessionData = useCallback(async () => {
try {
setLoading(true)
// This would typically call your backend API that queries LogRocket
// For demo purposes, we'll simulate the data structure
const mockSessionData: SessionData[] = [
{
id: 'session-1',
timestamp: Date.now() - 3600000,
duration: 1800000, // 30 minutes
userId: 'user-123',
events: [
{ type: 'page_view', timestamp: Date.now() - 3600000, data: { url: '/home' } },
{ type: 'click', timestamp: Date.now() - 3500000, data: { element: 'button' } },
{ type: 'form_submit', timestamp: Date.now() - 3400000, data: { formId: 'contact' } }
],
errors: [
{ message: 'TypeError: Cannot read property of undefined', stack: '...', timestamp: Date.now() - 3300000 }
],
pageViews: [
{ url: '/home', timestamp: Date.now() - 3600000, duration: 600000 },
{ url: '/products', timestamp: Date.now() - 3000000, duration: 1200000 }
]
}
]
setSessions(mockSessionData)
calculateMetrics(mockSessionData)
} catch (error) {
console.error('Failed to fetch session data:', error)
LogRocket.captureException(error as Error)
} finally {
setLoading(false)
}
}, [dateRange])
// Calculate analytics metrics
const calculateMetrics = (sessionData: SessionData[]) => {
const metrics: AnalyticsMetrics = {
totalSessions: sessionData.length,
averageSessionDuration: sessionData.reduce((acc, s) => acc + s.duration, 0) / sessionData.length,
errorRate: sessionData.filter(s => s.errors.length > 0).length / sessionData.length,
topPages: getTopPages(sessionData),
commonErrors: getCommonErrors(sessionData),
userRetention: calculateUserRetention(sessionData)
}
setMetrics(metrics)
// Track metrics calculation event
LogRocket.track('Analytics metrics calculated', {
totalSessions: metrics.totalSessions,
errorRate: metrics.errorRate,
averageSessionDuration: metrics.averageSessionDuration
})
}
// Get most viewed pages
const getTopPages = (sessionData: SessionData[]) => {
const pageViews: Record<string, number> = {}
sessionData.forEach(session => {
session.pageViews.forEach(view => {
pageViews[view.url] = (pageViews[view.url] || 0) + 1
})
})
return Object.entries(pageViews)
.sort(([, a], [, b]) => b - a)
.slice(0, 10)
.map(([url, views]) => ({ url, views }))
}
// Get most common errors
const getCommonErrors = (sessionData: SessionData[]) => {
const errors: Record<string, number> = {}
sessionData.forEach(session => {
session.errors.forEach(error => {
errors[error.message] = (errors[error.message] || 0) + 1
})
})
return Object.entries(errors)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([message, count]) => ({ message, count }))
}
// Calculate user retention metrics
const calculateUserRetention = (sessionData: SessionData[]) => {
// Simplified retention calculation
const now = Date.now()
const dayMs = 24 * 60 * 60 * 1000
return {
day1: Math.random() * 0.5 + 0.3, // 30-80%
day7: Math.random() * 0.3 + 0.1, // 10-40%
day30: Math.random() * 0.2 + 0.05 // 5-25%
}
}
// Filter sessions by date range
const getFilteredSessions = () => {
return sessions.filter(session => {
const sessionDate = new Date(session.timestamp)
return sessionDate >= dateRange.start && sessionDate <= dateRange.end
})
}
// Export session data
const exportData = () => {
const filteredSessions = getFilteredSessions()
const dataStr = JSON.stringify(filteredSessions, null, 2)
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr)
const exportFileDefaultName = `logrocket-sessions-${new Date().toISOString().split('T')[0]}.json`
const linkElement = document.createElement('a')
linkElement.setAttribute('href', dataUri)
linkElement.setAttribute('download', exportFileDefaultName)
linkElement.click()
LogRocket.track('Analytics data exported', {
sessionCount: filteredSessions.length,
dateRange
})
}
useEffect(() => {
fetchSessionData()
}, [fetchSessionData])
if (loading) {
return <div>Loading analytics data...</div>
}
if (!metrics) {
return <div>No data available</div>
}
return (
<div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
<h1>LogRocket Analytics Dashboard</h1>
{/* Date Range Selector */}
<div style={{ marginBottom: '20px', display: 'flex', gap: '10px', alignItems: 'center' }}>
<label>Date Range:</label>
<input
type="date"
value={dateRange.start.toISOString().split('T')[0]}
onChange={(e) => setDateRange(prev => ({ ...prev, start: new Date(e.target.value) }))}
/>
<span>to</span>
<input
type="date"
value={dateRange.end.toISOString().split('T')[0]}
onChange={(e) => setDateRange(prev => ({ ...prev, end: new Date(e.target.value) }))}
/>
<button onClick={fetchSessionData}>Refresh</button>
<button onClick={exportData}>Export Data</button>
</div>
{/* Key Metrics */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '20px', marginBottom: '30px' }}>
<div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h3>Total Sessions</h3>
<p style={{ fontSize: '24px', fontWeight: 'bold', color: '#0984e3' }}>{metrics.totalSessions}</p>
</div>
<div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h3>Avg Session Duration</h3>
<p style={{ fontSize: '24px', fontWeight: 'bold', color: '#00b894' }}>
{Math.round(metrics.averageSessionDuration / 1000 / 60)}m
</p>
</div>
<div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h3>Error Rate</h3>
<p style={{ fontSize: '24px', fontWeight: 'bold', color: '#d63031' }}>
{(metrics.errorRate * 100).toFixed(1)}%
</p>
</div>
<div style={{ padding: '15px', border: '1px solid #ddd', borderRadius: '8px' }}>
<h3>Day 1 Retention</h3>
<p style={{ fontSize: '24px', fontWeight: 'bold', color: '#fdcb6e' }}>
{(metrics.userRetention.day1 * 100).toFixed(0)}%
</p>
</div>
</div>
{/* Top Pages */}
<div style={{ marginBottom: '30px' }}>
<h2>Top Pages</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ backgroundColor: '#f8f9fa' }}>
<th style={{ padding: '10px', textAlign: 'left' }}>Page URL</th>
<th style={{ padding: '10px', textAlign: 'left' }}>Views</th>
</tr>
</thead>
<tbody>
{metrics.topPages.map((page, index) => (
<tr key={index}>
<td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{page.url}</td>
<td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{page.views}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Common Errors */}
<div style={{ marginBottom: '30px' }}>
<h2>Common Errors</h2>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ backgroundColor: '#f8f9fa' }}>
<th style={{ padding: '10px', textAlign: 'left' }}>Error Message</th>
<th style={{ padding: '10px', textAlign: 'left' }}>Count</th>
</tr>
</thead>
<tbody>
{metrics.commonErrors.map((error, index) => (
<tr key={index}>
<td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{error.message}</td>
<td style={{ padding: '10px', borderTop: '1px solid #ddd' }}>{error.count}</td>
</tr>
))}
</tbody>
</table>
</div>
{/* Sessions List */}
<div>
<h2>Recent Sessions</h2>
<div style={{ maxHeight: '300px', overflowY: 'auto' }}>
{getFilteredSessions().map((session) => (
<div
key={session.id}
style={{
padding: '10px',
border: '1px solid #ddd',
borderRadius: '4px',
marginBottom: '10px',
cursor: 'pointer'
}}
onClick={() => setSelectedSession(session)}
>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span><strong>Session:</strong> {session.id}</span>
<span>{new Date(session.timestamp).toLocaleString()}</span>
</div>
<div>
<small>Duration: {Math.round(session.duration / 1000 / 60)}m | Errors: {session.errors.length}</small>
</div>
</div>
))}
</div>
</div>
{/* Session Details Modal */}
{selectedSession && (
<div style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 1000
}}>
<div style={{
backgroundColor: 'white',
padding: '20px',
borderRadius: '8px',
maxWidth: '600px',
maxHeight: '80vh',
overflowY: 'auto'
}}>
<h2>Session Details</h2>
<p><strong>Session ID:</strong> {selectedSession.id}</p>
<p><strong>User ID:</strong> {selectedSession.userId}</p>
<p><strong>Duration:</strong> {Math.round(selectedSession.duration / 1000 / 60)} minutes</p>
<h3>Events</h3>
<div style={{ maxHeight: '200px', overflowY: 'auto' }}>
{selectedSession.events.map((event, index) => (
<div key={index} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
<small>{event.type} - {new Date(event.timestamp).toLocaleTimeString()}</small>
</div>
))}
</div>
<button
onClick={() => setSelectedSession(null)}
style={{ marginTop: '20px', padding: '10px 20px', cursor: 'pointer' }}
>
Close
</button>
</div>
</div>
)}
</div>
)
}
export default LogRocketAnalyticsDashboard