fix: skeleton loading states match actual page layouts
- PageSpeed: show 4 gauge rings, screenshot, legend, metrics grid, trend chart - Uptime: match real layout with status card, 90-day bar, 4-col detail grid - Remove duplicate local skeletons in behavior components, use shared library - Strip light-mode classes from dark-only app
This commit is contained in:
@@ -3,30 +3,13 @@
|
||||
import { formatNumber } from '@ciphera-net/ui'
|
||||
import { Files } from '@phosphor-icons/react'
|
||||
import type { FrustrationByPage } from '@/lib/api/stats'
|
||||
import { TableSkeleton } from '@/components/skeletons'
|
||||
|
||||
interface FrustrationByPageTableProps {
|
||||
pages: FrustrationByPage[]
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
function SkeletonRows() {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<div key={i} className="animate-pulse flex items-center justify-between h-9 px-2">
|
||||
<div className="h-4 w-40 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="flex gap-6">
|
||||
<div className="h-4 w-10 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-4 w-10 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-4 w-10 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-4 w-10 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FrustrationByPageTable({ pages, loading }: FrustrationByPageTableProps) {
|
||||
const hasData = pages.length > 0
|
||||
const maxTotal = Math.max(...pages.map(p => p.total), 1)
|
||||
@@ -43,7 +26,7 @@ export default function FrustrationByPageTable({ pages, loading }: FrustrationBy
|
||||
</p>
|
||||
|
||||
{loading ? (
|
||||
<SkeletonRows />
|
||||
<TableSkeleton rows={5} cols={5} />
|
||||
) : hasData ? (
|
||||
<div className="overflow-x-auto -mx-6 px-6">
|
||||
{/* Header */}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { FrustrationSummary } from '@/lib/api/stats'
|
||||
import { StatCardSkeleton } from '@/components/skeletons'
|
||||
|
||||
interface FrustrationSummaryCardsProps {
|
||||
data: FrustrationSummary | null
|
||||
@@ -39,25 +40,13 @@ function ChangeIndicator({ change }: { change: ReturnType<typeof pctChange> }) {
|
||||
)
|
||||
}
|
||||
|
||||
function SkeletonCard() {
|
||||
return (
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6">
|
||||
<div className="animate-pulse space-y-3">
|
||||
<div className="h-4 w-24 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-8 w-16 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-3 w-32 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function FrustrationSummaryCards({ data, loading }: FrustrationSummaryCardsProps) {
|
||||
if (loading || !data) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
|
||||
<SkeletonCard />
|
||||
<SkeletonCard />
|
||||
<SkeletonCard />
|
||||
<StatCardSkeleton />
|
||||
<StatCardSkeleton />
|
||||
<StatCardSkeleton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,26 +8,13 @@ import {
|
||||
type ChartConfig,
|
||||
} from '@/components/charts'
|
||||
import type { FrustrationSummary } from '@/lib/api/stats'
|
||||
import { WidgetSkeleton } from '@/components/skeletons'
|
||||
|
||||
interface FrustrationTrendProps {
|
||||
summary: FrustrationSummary | null
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
function SkeletonCard() {
|
||||
return (
|
||||
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 h-full flex flex-col">
|
||||
<div className="animate-pulse space-y-3 mb-4">
|
||||
<div className="h-5 w-36 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
<div className="h-4 w-48 bg-neutral-200 dark:bg-neutral-700 rounded" />
|
||||
</div>
|
||||
<div className="flex-1 min-h-[270px] animate-pulse flex items-center justify-center">
|
||||
<div className="w-[200px] h-[200px] rounded-full bg-neutral-200 dark:bg-neutral-700" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const LABELS: Record<string, string> = {
|
||||
rage_clicks: 'Rage Clicks',
|
||||
dead_clicks: 'Dead Clicks',
|
||||
@@ -70,7 +57,7 @@ function CustomTooltip({ active, payload }: { active?: boolean; payload?: Array<
|
||||
}
|
||||
|
||||
export default function FrustrationTrend({ summary, loading }: FrustrationTrendProps) {
|
||||
if (loading || !summary) return <SkeletonCard />
|
||||
if (loading || !summary) return <WidgetSkeleton />
|
||||
|
||||
const hasData = summary.rage_clicks > 0 || summary.dead_clicks > 0 ||
|
||||
summary.prev_rage_clicks > 0 || summary.prev_dead_clicks > 0
|
||||
|
||||
Reference in New Issue
Block a user