Client UX: toast system, confirm dialogs, load-error banners, nav fix, 404 page
- New ToastProvider/useToast (self-built, no dep): success/error toasts replace
all five alert() calls, with server error messages surfaced
- New ConfirmProvider/useConfirm promise-based dialog replaces all six
window.confirm sites, showing what is being deleted; Escape/overlay cancels
- Dashboard + BreedingCalendar loaders use Promise.allSettled with a
danger banner + Retry instead of silently blanking on fetch failure
- Settings load failure now surfaces a toast instead of vanishing
- Fix BreedingCalendar females list: read paginated {data} shape (was .dogs,
so 'Start Heat Cycle' never listed any females)
- Nav links highlight on detail routes (/dogs/123, /pedigree/5)
- Catch-all 404 route with link back to Dashboard
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+36
-6
@@ -1,5 +1,6 @@
|
||||
import { useEffect } from 'react'
|
||||
import { BrowserRouter as Router, Routes, Route, Link, useLocation } from 'react-router-dom'
|
||||
import { Home, PawPrint, Activity, Heart, FlaskConical, Settings, ExternalLink } from 'lucide-react'
|
||||
import { Home, PawPrint, Activity, Heart, FlaskConical, Settings, ExternalLink, SearchX } from 'lucide-react'
|
||||
import Dashboard from './pages/Dashboard'
|
||||
import DogList from './pages/DogList'
|
||||
import DogDetail from './pages/DogDetail'
|
||||
@@ -11,11 +12,17 @@ import PairingSimulator from './pages/PairingSimulator'
|
||||
import SettingsPage from './pages/SettingsPage'
|
||||
import ExternalDogs from './pages/ExternalDogs'
|
||||
import { useSettings } from './hooks/useSettings'
|
||||
import { ToastProvider, useToast } from './components/Toast'
|
||||
import { ConfirmProvider } from './components/ConfirmDialog'
|
||||
import './App.css'
|
||||
|
||||
function NavLink({ to, icon: Icon, label }) {
|
||||
// `match` lets a nav item claim extra path prefixes (e.g. Dogs owns /pedigree/:id)
|
||||
function NavLink({ to, icon: Icon, label, match = [] }) {
|
||||
const location = useLocation()
|
||||
const isActive = location.pathname === to
|
||||
const prefixes = [to, ...match]
|
||||
const isActive = to === '/'
|
||||
? location.pathname === '/'
|
||||
: prefixes.some(p => location.pathname === p || location.pathname.startsWith(p + '/'))
|
||||
return (
|
||||
<Link to={to} className={`nav-link${isActive ? ' active' : ''}`}>
|
||||
<Icon size={20} />
|
||||
@@ -24,10 +31,28 @@ function NavLink({ to, icon: Icon, label }) {
|
||||
)
|
||||
}
|
||||
|
||||
function NotFound() {
|
||||
return (
|
||||
<div className="container" style={{ textAlign: 'center', padding: '4rem 1rem' }}>
|
||||
<SearchX size={48} color="var(--text-muted)" style={{ marginBottom: '1rem' }} />
|
||||
<h2 style={{ marginBottom: '0.5rem' }}>Page not found</h2>
|
||||
<p style={{ color: 'var(--text-muted)', marginBottom: '1.5rem' }}>
|
||||
That page doesn't exist or may have been moved.
|
||||
</p>
|
||||
<Link to="/" className="btn btn-primary">Back to Dashboard</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function AppInner() {
|
||||
const { settings } = useSettings()
|
||||
const { settings, loadError } = useSettings()
|
||||
const toast = useToast()
|
||||
const kennelName = settings?.kennel_name || 'BREEDR'
|
||||
|
||||
useEffect(() => {
|
||||
if (loadError) toast.error('Failed to load kennel settings — using defaults')
|
||||
}, [loadError, toast])
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<nav className="navbar">
|
||||
@@ -42,7 +67,7 @@ function AppInner() {
|
||||
</div>
|
||||
<div className="nav-links">
|
||||
<NavLink to="/" icon={Home} label="Dashboard" />
|
||||
<NavLink to="/dogs" icon={PawPrint} label="Dogs" />
|
||||
<NavLink to="/dogs" icon={PawPrint} label="Dogs" match={['/pedigree']} />
|
||||
<NavLink to="/external" icon={ExternalLink} label="External" />
|
||||
<NavLink to="/litters" icon={Activity} label="Litters" />
|
||||
<NavLink to="/breeding" icon={Heart} label="Breeding" />
|
||||
@@ -64,6 +89,7 @@ function AppInner() {
|
||||
<Route path="/breeding" element={<BreedingCalendar />} />
|
||||
<Route path="/pairing" element={<PairingSimulator />} />
|
||||
<Route path="/settings" element={<SettingsPage />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</main>
|
||||
</div>
|
||||
@@ -73,7 +99,11 @@ function AppInner() {
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<AppInner />
|
||||
<ToastProvider>
|
||||
<ConfirmProvider>
|
||||
<AppInner />
|
||||
</ConfirmProvider>
|
||||
</ToastProvider>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user