feat: enhance error handling in FunnelReportPage by adding specific messages for not found and general load errors, improving user feedback and experience
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useParams, useRouter } from 'next/navigation'
|
import { useParams, useRouter } from 'next/navigation'
|
||||||
|
import { ApiError } from '@/lib/api/client'
|
||||||
import { getFunnel, getFunnelStats, deleteFunnel, type Funnel, type FunnelStats } from '@/lib/api/funnels'
|
import { getFunnel, getFunnelStats, deleteFunnel, type Funnel, type FunnelStats } from '@/lib/api/funnels'
|
||||||
import { toast, LoadingOverlay, Select, DatePicker, ChevronLeftIcon, ArrowRightIcon, TrashIcon, useTheme } from '@ciphera-net/ui'
|
import { toast, LoadingOverlay, Select, DatePicker, ChevronLeftIcon, ArrowRightIcon, TrashIcon, useTheme } from '@ciphera-net/ui'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
@@ -45,8 +46,10 @@ export default function FunnelReportPage() {
|
|||||||
const [dateRange, setDateRange] = useState(getDateRange(30))
|
const [dateRange, setDateRange] = useState(getDateRange(30))
|
||||||
const [datePreset, setDatePreset] = useState<'7' | '30' | 'custom'>('30')
|
const [datePreset, setDatePreset] = useState<'7' | '30' | 'custom'>('30')
|
||||||
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
|
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false)
|
||||||
|
const [loadError, setLoadError] = useState<'not_found' | 'error' | null>(null)
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
|
setLoadError(null)
|
||||||
try {
|
try {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const [funnelData, statsData] = await Promise.all([
|
const [funnelData, statsData] = await Promise.all([
|
||||||
@@ -56,7 +59,9 @@ export default function FunnelReportPage() {
|
|||||||
setFunnel(funnelData)
|
setFunnel(funnelData)
|
||||||
setStats(statsData)
|
setStats(statsData)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to load funnel data')
|
const is404 = error instanceof ApiError && error.status === 404
|
||||||
|
setLoadError(is404 ? 'not_found' : 'error')
|
||||||
|
if (!is404) toast.error('Failed to load funnel data')
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
@@ -88,6 +93,25 @@ export default function FunnelReportPage() {
|
|||||||
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
|
return <LoadingOverlay logoSrc="/pulse_icon_no_margins.png" title="Pulse" />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (loadError === 'not_found' || (!funnel && !stats && !loadError)) {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
<p className="text-neutral-600 dark:text-neutral-400">Funnel not found</p>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadError === 'error') {
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
<p className="text-neutral-600 dark:text-neutral-400 mb-4">Failed to load funnel data</p>
|
||||||
|
<button type="button" onClick={() => loadData()} className="btn-primary">
|
||||||
|
Try again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!funnel || !stats) {
|
if (!funnel || !stats) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
|
<div className="w-full max-w-6xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ export default function CreateFunnelPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (steps.some(s => !s.name.trim())) {
|
||||||
|
toast.error('Please enter a name for all steps')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (steps.some(s => !s.value.trim())) {
|
if (steps.some(s => !s.value.trim())) {
|
||||||
toast.error('Please enter a path for all steps')
|
toast.error('Please enter a path for all steps')
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user