style: add skeleton loading & fade transition to behavior page

This commit is contained in:
Usman Baig
2026-03-13 14:30:01 +01:00
parent 00d232ab3f
commit b5d408b4e8
3 changed files with 57 additions and 2 deletions

View File

@@ -12,7 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
- **Easier to hover country dots on the map.** The orange location markers on the world map are now much easier to interact with — you no longer need pixel-perfect aim to see the tooltip. - **Easier to hover country dots on the map.** The orange location markers on the world map are now much easier to interact with — you no longer need pixel-perfect aim to see the tooltip.
- **Smoother chart curves and filled area.** The dashboard chart line now flows with natural curves instead of sharp flat tops at peaks. The area beneath the line is filled with a soft transparent orange gradient that fades toward the bottom, making trends easier to read at a glance. - **Smoother chart curves and filled area.** The dashboard chart line now flows with natural curves instead of sharp flat tops at peaks. The area beneath the line is filled with a soft transparent orange gradient that fades toward the bottom, making trends easier to read at a glance.
- **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look. - **Refreshed chart background.** The dashboard chart now has subtle horizontal lines instead of the old dotted background, giving the chart area a cleaner look.
- **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data. - **Smoother loading transitions.** When your data finishes loading, the page now fades in smoothly instead of appearing all at once. This applies across Dashboard, Journeys, Funnels, Behavior, Uptime, Settings, Notifications, and shared dashboards. If your data was already cached from a previous visit, it still loads instantly with no animation — the fade only kicks in when you're actually waiting for fresh data.
- **Faster tab switching across the board.** Switching between Settings, Funnels, Uptime, and other tabs now shows your data instantly instead of flashing a loading skeleton every time. Previously visited tabs remember their data and show it right away, while quietly refreshing in the background so you always see the latest numbers without the wait. - **Faster tab switching across the board.** Switching between Settings, Funnels, Uptime, and other tabs now shows your data instantly instead of flashing a loading skeleton every time. Previously visited tabs remember their data and show it right away, while quietly refreshing in the background so you always see the latest numbers without the wait.
- **Smoother loading on the Journeys page.** The Journeys tab now shows a polished skeleton placeholder while data loads, matching the loading experience on other tabs. - **Smoother loading on the Journeys page.** The Journeys tab now shows a polished skeleton placeholder while data loads, matching the loading experience on other tabs.
- **Consistent chart colors.** All dashboard charts — Unique Visitors, Total Pageviews, Bounce Rate, and Visit Duration — now use the same brand orange color for a cleaner, more cohesive look. - **Consistent chart colors.** All dashboard charts — Unique Visitors, Total Pageviews, Bounce Rate, and Visit Duration — now use the same brand orange color for a cleaner, more cohesive look.

View File

@@ -11,6 +11,7 @@ import FrustrationTable from '@/components/behavior/FrustrationTable'
import FrustrationByPageTable from '@/components/behavior/FrustrationByPageTable' import FrustrationByPageTable from '@/components/behavior/FrustrationByPageTable'
import FrustrationTrend from '@/components/behavior/FrustrationTrend' import FrustrationTrend from '@/components/behavior/FrustrationTrend'
import { useDashboard, useBehavior } from '@/lib/swr/dashboard' import { useDashboard, useBehavior } from '@/lib/swr/dashboard'
import { BehaviorSkeleton, useMinimumLoading, useSkeletonFade } from '@/components/skeletons'
const ScrollDepth = dynamic(() => import('@/components/dashboard/ScrollDepth')) const ScrollDepth = dynamic(() => import('@/components/dashboard/ScrollDepth'))
@@ -42,6 +43,9 @@ export default function BehaviorPage() {
// Fetch dashboard data for scroll depth (goal_counts + stats) // Fetch dashboard data for scroll depth (goal_counts + stats)
const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end) const { data: dashboard } = useDashboard(siteId, dateRange.start, dateRange.end)
const showSkeleton = useMinimumLoading(loading && !behavior)
const fadeClass = useSkeletonFade(showSkeleton)
useEffect(() => { useEffect(() => {
const domain = dashboard?.site?.domain const domain = dashboard?.site?.domain
document.title = domain ? `Behavior · ${domain} | Pulse` : 'Behavior | Pulse' document.title = domain ? `Behavior · ${domain} | Pulse` : 'Behavior | Pulse'
@@ -63,8 +67,10 @@ export default function BehaviorPage() {
const deadClicks = behavior?.dead_clicks ?? { items: [], total: 0 } const deadClicks = behavior?.dead_clicks ?? { items: [], total: 0 }
const byPage = behavior?.by_page ?? [] const byPage = behavior?.by_page ?? []
if (showSkeleton) return <BehaviorSkeleton />
return ( return (
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8"> <div className={`w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8 ${fadeClass}`}>
{/* Header */} {/* Header */}
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4"> <div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div> <div>

View File

@@ -370,6 +370,55 @@ export function PricingCardsSkeleton() {
) )
} }
// ─── Behavior page skeleton ─────────────────────────────────
export function BehaviorSkeleton() {
return (
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 pb-8">
{/* Header */}
<div className="mb-8 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<SkeletonLine className="h-8 w-32 mb-2" />
<SkeletonLine className="h-4 w-64" />
</div>
<SkeletonLine className="h-9 w-36 rounded-lg" />
</div>
{/* Summary cards (3 cols) */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4 mb-8">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6 space-y-3">
<SkeletonLine className="h-4 w-24" />
<SkeletonLine className="h-8 w-16" />
<SkeletonLine className="h-3 w-32" />
</div>
))}
</div>
{/* Rage clicks + Dead clicks side by side */}
<div className="grid gap-6 lg:grid-cols-2 mb-8">
<WidgetSkeleton />
<WidgetSkeleton />
</div>
{/* By-page table */}
<div className="mb-8">
<div className="bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-2xl p-6">
<SkeletonLine className="h-6 w-40 mb-2" />
<SkeletonLine className="h-4 w-64 mb-4" />
<TableSkeleton rows={5} cols={4} />
</div>
</div>
{/* Scroll depth + Frustration trend */}
<div className="grid gap-6 lg:grid-cols-2 mb-8">
<WidgetSkeleton />
<WidgetSkeleton />
</div>
</div>
)
}
// ─── Organization settings skeleton (members, billing, etc) ─ // ─── Organization settings skeleton (members, billing, etc) ─
export function MembersListSkeleton() { export function MembersListSkeleton() {