🎯 Exemples recommandés
Balanced sample collections from various categories for you to explore
Surveillance Frontend LogRocket
Exemples LogRocket pour la relecture de session, le suivi d'erreurs, la surveillance de performance et l'analyse du comportement utilisateur
💻 LogRocket Hello World typescript
🟢 simple
⭐
Configuration de base LogRocket avec enregistrement de session et initialisation
⏱️ 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()
})
})
💻 Relecture de Session Avancée typescript
🟡 intermediate
⭐⭐⭐
Enregistrement de session configurable avec événements personnalisés et filtres
⏱️ 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 }
💻 Intégration React Error Boundary typescript
🟡 intermediate
⭐⭐⭐
React error boundary avec intégration LogRocket pour un suivi complet des erreurs
⏱️ 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
}
💻 Tableau de Bord Analytique Personnalisé typescript
🔴 complex
⭐⭐⭐⭐
Construire un tableau de bord analytique personnalisé en utilisant les données et insights 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