perf: migrate Settings, Funnels, and Uptime to SWR data fetching

Replace manual useState/useEffect fetch patterns with SWR hooks so
cached data renders instantly on tab revisit. Skeleton loading now
only appears on the initial cold load, not every navigation.

New hooks: useFunnels, useUptimeStatus, useGoals, useReportSchedules,
useSubscription — all with background revalidation.
This commit is contained in:
Usman Baig
2026-03-13 12:21:55 +01:00
parent b6a7c642f2
commit 6380f216aa
5 changed files with 143 additions and 190 deletions

View File

@@ -1,12 +1,11 @@
'use client'
import { useAuth } from '@/lib/auth/context'
import { useEffect, useState, useCallback, useRef } from 'react'
import { useEffect, useState, useRef } from 'react'
import { useParams, useRouter } from 'next/navigation'
import { motion, AnimatePresence } from 'framer-motion'
import { getSite, type Site } from '@/lib/api/sites'
import { useSite, useUptimeStatus } from '@/lib/swr/dashboard'
import {
getUptimeStatus,
createUptimeMonitor,
updateUptimeMonitor,
deleteUptimeMonitor,
@@ -561,9 +560,8 @@ export default function UptimePage() {
const router = useRouter()
const siteId = params.id as string
const [site, setSite] = useState<Site | null>(null)
const [loading, setLoading] = useState(true)
const [uptimeData, setUptimeData] = useState<UptimeStatusResponse | null>(null)
const { data: site } = useSite(siteId)
const { data: uptimeData, isLoading, mutate: mutateUptime } = useUptimeStatus(siteId)
const [expandedMonitor, setExpandedMonitor] = useState<string | null>(null)
const [showAddModal, setShowAddModal] = useState(false)
const [showEditModal, setShowEditModal] = useState(false)
@@ -577,38 +575,6 @@ export default function UptimePage() {
})
const [saving, setSaving] = useState(false)
const loadData = useCallback(async () => {
try {
const [siteData, statusData] = await Promise.all([
getSite(siteId),
getUptimeStatus(siteId),
])
setSite(siteData)
setUptimeData(statusData)
} catch (error: unknown) {
toast.error(getAuthErrorMessage(error) || 'Failed to load uptime monitors')
} finally {
setLoading(false)
}
}, [siteId])
useEffect(() => {
loadData()
}, [loadData])
// * Auto-refresh every 30 seconds; show toast on failure (e.g. network loss or auth expiry)
useEffect(() => {
const interval = setInterval(async () => {
try {
const statusData = await getUptimeStatus(siteId)
setUptimeData(statusData)
} catch {
toast.error('Could not refresh uptime data. Check your connection or sign in again.')
}
}, 30000)
return () => clearInterval(interval)
}, [siteId])
const handleAddMonitor = async () => {
if (!formData.name || !formData.url) {
toast.error('Name and URL are required')
@@ -620,7 +586,7 @@ export default function UptimePage() {
toast.success('Monitor created successfully')
setShowAddModal(false)
setFormData({ name: '', url: '', check_interval_seconds: 300, expected_status_code: 200, timeout_seconds: 30 })
await loadData()
mutateUptime()
} catch (error: unknown) {
toast.error(getAuthErrorMessage(error) || 'Failed to create monitor')
} finally {
@@ -643,7 +609,7 @@ export default function UptimePage() {
toast.success('Monitor updated successfully')
setShowEditModal(false)
setEditingMonitor(null)
await loadData()
mutateUptime()
} catch (error: unknown) {
toast.error(getAuthErrorMessage(error) || 'Failed to update monitor')
} finally {
@@ -656,7 +622,7 @@ export default function UptimePage() {
try {
await deleteUptimeMonitor(siteId, monitorId)
toast.success('Monitor deleted')
await loadData()
mutateUptime()
} catch (error: unknown) {
toast.error(getAuthErrorMessage(error) || 'Failed to delete monitor')
}
@@ -678,7 +644,7 @@ export default function UptimePage() {
if (site?.domain) document.title = `Uptime · ${site.domain} | Pulse`
}, [site?.domain])
const showSkeleton = useMinimumLoading(loading)
const showSkeleton = useMinimumLoading(isLoading && !uptimeData)
if (showSkeleton) return <UptimeSkeleton />
if (!site) return <div className="p-8 text-neutral-500">Site not found</div>