refactor: remove performance insights (Web Vitals) feature entirely
Remove Performance tab, PerformanceStats component, settings toggle, Web Vitals observers from tracking script, and all related API types and SWR hooks. Duration tracking is preserved.
This commit is contained in:
@@ -234,9 +234,9 @@ export default function SiteDashboardPage() {
|
||||
return { start: prevStart.toISOString().split('T')[0], end: prevEnd.toISOString().split('T')[0] }
|
||||
}, [dateRange])
|
||||
|
||||
// Single dashboard request replaces 7 focused hooks (overview, pages, locations,
|
||||
// devices, referrers, performance, goals). The backend runs all queries in parallel
|
||||
// and caches the result in Redis, reducing requests from 12 to 6 per refresh cycle.
|
||||
// Single dashboard request replaces focused hooks (overview, pages, locations,
|
||||
// devices, referrers, goals). The backend runs all queries in parallel
|
||||
// and caches the result in Redis for efficient data loading.
|
||||
const { data: dashboard, isLoading: dashboardLoading, error: dashboardError } = useDashboard(siteId, dateRange.start, dateRange.end, interval, filtersParam || undefined)
|
||||
const { data: realtimeData } = useRealtime(siteId)
|
||||
const { data: prevStats } = useStats(siteId, prevRange.start, prevRange.end)
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { getDateRange, formatDate } from '@ciphera-net/ui'
|
||||
import { Select, DatePicker } from '@ciphera-net/ui'
|
||||
import { getPerformanceByPage } from '@/lib/api/stats'
|
||||
import { useDashboard } from '@/lib/swr/dashboard'
|
||||
import { useMinimumLoading, useSkeletonFade } from '@/components/skeletons'
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const PerformanceStats = dynamic(() => import('@/components/dashboard/PerformanceStats'))
|
||||
|
||||
function getThisWeekRange(): { start: string; end: string } {
|
||||
const today = new Date()
|
||||
const dayOfWeek = today.getDay()
|
||||
const monday = new Date(today)
|
||||
monday.setDate(today.getDate() - (dayOfWeek === 0 ? 6 : dayOfWeek - 1))
|
||||
return { start: formatDate(monday), end: formatDate(today) }
|
||||
}
|
||||
|
||||
function getThisMonthRange(): { start: string; end: string } {
|
||||
const today = new Date()
|
||||
const firstOfMonth = new Date(today.getFullYear(), today.getMonth(), 1)
|
||||
return { start: formatDate(firstOfMonth), end: formatDate(today) }
|
||||
}
|
||||
|
||||
function PerformanceSkeleton() {
|
||||
return (
|
||||
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8 animate-pulse">
|
||||
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<div className="h-7 w-40 bg-neutral-200 dark:bg-neutral-800 rounded mb-2" />
|
||||
<div className="h-4 w-64 bg-neutral-200 dark:bg-neutral-800 rounded" />
|
||||
</div>
|
||||
<div className="h-9 w-36 bg-neutral-200 dark:bg-neutral-800 rounded" />
|
||||
</div>
|
||||
<div className="h-6 w-24 bg-neutral-200 dark:bg-neutral-800 rounded mb-6" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="h-24 bg-neutral-200 dark:bg-neutral-800 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
<div className="h-64 bg-neutral-200 dark:bg-neutral-800 rounded-2xl" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function PerformancePage() {
|
||||
const params = useParams()
|
||||
const siteId = params.id as string
|
||||
|
||||
const [period, setPeriod] = useState('30')
|
||||
const [dateRange, setDateRange] = useState(() => getDateRange(30))
|
||||
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
|
||||
|
||||
const { data: dashboard, isLoading: loading } = useDashboard(siteId, dateRange.start, dateRange.end)
|
||||
|
||||
const site = dashboard?.site ?? null
|
||||
const showSkeleton = useMinimumLoading(loading && !dashboard)
|
||||
const fadeClass = useSkeletonFade(showSkeleton)
|
||||
|
||||
useEffect(() => {
|
||||
const domain = site?.domain
|
||||
document.title = domain ? `Performance \u00b7 ${domain} | Pulse` : 'Performance | Pulse'
|
||||
}, [site?.domain])
|
||||
|
||||
if (showSkeleton) return <PerformanceSkeleton />
|
||||
|
||||
if (site && !site.enable_performance_insights) {
|
||||
return (
|
||||
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8">
|
||||
<div className="text-center py-16">
|
||||
<h2 className="text-lg font-semibold text-neutral-900 dark:text-white mb-2">
|
||||
Performance insights are disabled
|
||||
</h2>
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
Enable performance insights in your site settings to start collecting Core Web Vitals data.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8 ${fadeClass}`}>
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-neutral-900 dark:text-white mb-1">
|
||||
Performance
|
||||
</h1>
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
Core Web Vitals from real user sessions
|
||||
</p>
|
||||
</div>
|
||||
<Select
|
||||
variant="input"
|
||||
className="min-w-[140px]"
|
||||
value={period}
|
||||
onChange={(value) => {
|
||||
if (value === 'today') {
|
||||
const today = formatDate(new Date())
|
||||
setDateRange({ start: today, end: today })
|
||||
setPeriod('today')
|
||||
} else if (value === '7') {
|
||||
setDateRange(getDateRange(7))
|
||||
setPeriod('7')
|
||||
} else if (value === 'week') {
|
||||
setDateRange(getThisWeekRange())
|
||||
setPeriod('week')
|
||||
} else if (value === '30') {
|
||||
setDateRange(getDateRange(30))
|
||||
setPeriod('30')
|
||||
} else if (value === 'month') {
|
||||
setDateRange(getThisMonthRange())
|
||||
setPeriod('month')
|
||||
} else if (value === 'custom') {
|
||||
setIsDatePickerOpen(true)
|
||||
}
|
||||
}}
|
||||
options={[
|
||||
{ value: 'today', label: 'Today' },
|
||||
{ value: '7', label: 'Last 7 days' },
|
||||
{ value: '30', label: 'Last 30 days' },
|
||||
{ value: 'divider-1', label: '', divider: true },
|
||||
{ value: 'week', label: 'This week' },
|
||||
{ value: 'month', label: 'This month' },
|
||||
{ value: 'divider-2', label: '', divider: true },
|
||||
{ value: 'custom', label: 'Custom' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<PerformanceStats
|
||||
stats={dashboard?.performance ?? null}
|
||||
performanceByPage={dashboard?.performance_by_page ?? null}
|
||||
siteId={siteId}
|
||||
startDate={dateRange.start}
|
||||
endDate={dateRange.end}
|
||||
getPerformanceByPage={getPerformanceByPage}
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
isOpen={isDatePickerOpen}
|
||||
onClose={() => setIsDatePickerOpen(false)}
|
||||
onApply={(range) => {
|
||||
setDateRange(range)
|
||||
setPeriod('custom')
|
||||
setIsDatePickerOpen(false)
|
||||
}}
|
||||
initialRange={dateRange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -74,8 +74,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: true,
|
||||
collect_geo_data: 'full' as GeoDataLevel,
|
||||
collect_screen_resolution: true,
|
||||
// Performance insights setting
|
||||
enable_performance_insights: false,
|
||||
// Bot and noise filtering
|
||||
filter_bots: true,
|
||||
// Hide unknown locations
|
||||
@@ -135,7 +133,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: site.collect_device_info ?? true,
|
||||
collect_geo_data: site.collect_geo_data || 'full',
|
||||
collect_screen_resolution: site.collect_screen_resolution ?? true,
|
||||
enable_performance_insights: site.enable_performance_insights ?? false,
|
||||
filter_bots: site.filter_bots ?? true,
|
||||
hide_unknown_locations: site.hide_unknown_locations ?? false,
|
||||
data_retention_months: site.data_retention_months ?? 6
|
||||
@@ -150,7 +147,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: site.collect_device_info ?? true,
|
||||
collect_geo_data: site.collect_geo_data || 'full',
|
||||
collect_screen_resolution: site.collect_screen_resolution ?? true,
|
||||
enable_performance_insights: site.enable_performance_insights ?? false,
|
||||
filter_bots: site.filter_bots ?? true,
|
||||
hide_unknown_locations: site.hide_unknown_locations ?? false,
|
||||
data_retention_months: site.data_retention_months ?? 6
|
||||
@@ -423,8 +419,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: formData.collect_device_info,
|
||||
collect_geo_data: formData.collect_geo_data,
|
||||
collect_screen_resolution: formData.collect_screen_resolution,
|
||||
// Performance insights setting
|
||||
enable_performance_insights: formData.enable_performance_insights,
|
||||
// Bot and noise filtering
|
||||
filter_bots: formData.filter_bots,
|
||||
// Hide unknown locations
|
||||
@@ -443,7 +437,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: formData.collect_device_info,
|
||||
collect_geo_data: formData.collect_geo_data,
|
||||
collect_screen_resolution: formData.collect_screen_resolution,
|
||||
enable_performance_insights: formData.enable_performance_insights,
|
||||
filter_bots: formData.filter_bots,
|
||||
hide_unknown_locations: formData.hide_unknown_locations,
|
||||
data_retention_months: formData.data_retention_months
|
||||
@@ -511,7 +504,6 @@ export default function SiteSettingsPage() {
|
||||
collect_device_info: formData.collect_device_info,
|
||||
collect_geo_data: formData.collect_geo_data,
|
||||
collect_screen_resolution: formData.collect_screen_resolution,
|
||||
enable_performance_insights: formData.enable_performance_insights,
|
||||
filter_bots: formData.filter_bots,
|
||||
hide_unknown_locations: formData.hide_unknown_locations,
|
||||
data_retention_months: formData.data_retention_months
|
||||
@@ -1113,30 +1105,6 @@ export default function SiteSettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Performance Insights Toggle */}
|
||||
<div className="space-y-4 pt-6 border-t border-neutral-100 dark:border-neutral-800">
|
||||
<h3 className="text-sm font-medium text-neutral-700 dark:text-neutral-300">Performance Insights</h3>
|
||||
<div className="p-6 bg-neutral-50 dark:bg-neutral-900/50 rounded-2xl border border-neutral-100 dark:border-neutral-800">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h4 className="font-medium text-neutral-900 dark:text-white">Performance Insights (Add-on)</h4>
|
||||
<p className="text-sm text-neutral-500 dark:text-neutral-400 mt-0.5">
|
||||
Track Core Web Vitals (LCP, CLS, INP) to monitor site performance
|
||||
</p>
|
||||
</div>
|
||||
<label className="relative inline-flex items-center cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={formData.enable_performance_insights}
|
||||
onChange={(e) => setFormData({ ...formData, enable_performance_insights: e.target.checked })}
|
||||
className="sr-only peer"
|
||||
/>
|
||||
<div className="w-11 h-6 bg-neutral-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-brand-orange/20 dark:peer-focus:ring-brand-orange/20 rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-neutral-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-neutral-600 peer-checked:bg-brand-orange"></div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data Retention */}
|
||||
<div className="space-y-4 pt-6 border-t border-neutral-100 dark:border-neutral-800">
|
||||
<h3 className="text-sm font-medium text-neutral-700 dark:text-neutral-300">Data Retention</h3>
|
||||
|
||||
Reference in New Issue
Block a user