Public Access
feat(ui): global react-query error handler + plan-status a11y (Sprint 4 F7+F6)
F7: surface every failed query/mutation as a toast via react-query
QueryCache/MutationCache onError, with a single error normalizer that
extracts FastAPI's response.data.detail (string or Pydantic 422 array).
- lib/toast.tsx: new extractErrorMessage(err, fallback) and
showApiError(err, fallback). Reads response.data.detail when present
(string or [{loc, msg, type}, ...] array), then err.message, then
the fallback. No more '[object Object]' or raw stack traces.
- App.tsx: QueryClient is now created with QueryCache and
MutationCache onError handlers wired to showApiError. Added
defaultOptions.queries: { retry: 1, refetchOnWindowFocus: false }
so background refetch failures are no longer silent (the audit's
H9 finding).
- Dashboard.tsx: removed 6 local try/catch toasts (move/approve/deny/
delete/generate) since the global handler now covers them. Kept
VoteEmailButton.handleSend and handleDelete's undo-callback with
showApiError(err, 'Failed to ...') for action-specific fallback
strings — those are user-initiated recovery paths where a contextual
default is more useful than the bare FastAPI detail.
- Pantry.tsx: removed 3 local onError handlers (addMutation,
removeMutation, handleAdd's createIngredient path) and
handleRemove's outer catch. Kept 3 pre-flight client-side checks
(missing ingredient link, empty name, unresolved ingredient) that
never reach the network. handleRemove's undo callback now uses
showApiError for the restore failure.
- MealDetail.tsx: removed submitMutation.onError. The local
'Failed to save feedback. Please try again.' string is replaced
by the actual FastAPI detail (e.g. 'Feedback for this meal already
exists' or the Pydantic 422 msg).
Net result: 10 backend-error try/catch blocks deleted, error messages
are now identical to what the backend actually says, and any future
mutation that forgets to add a local onError still gets surfaced.
F6: Dashboard plan-status Badge (variant driven by status: draft /
awaiting_approval / approved / rejected) now passes an explicit
aria-label='Plan status: <text>' so a screen reader announces both
the category and the value instead of just the colour-encoded text.
This matches the pattern already used for the per-item approval
status Badge in Dashboard.tsx (added in Sprint 3) and completes the
audit §Sprint 3 a11y sweep for that page.
build: tsc 0 errors, vite 0 errors. 5 files, +72/-19.
This commit is contained in:
@@ -9,7 +9,7 @@ import { Input } from '../components/ui/Input'
|
||||
import { Select } from '../components/ui/Select'
|
||||
import { Skeleton, SkeletonText } from '../components/ui/Skeleton'
|
||||
import { EmptyState } from '../components/ui/EmptyState'
|
||||
import { showToast } from '../lib/toast'
|
||||
import { showToast, showApiError } from '../lib/toast'
|
||||
|
||||
const AISLE_OPTIONS = [
|
||||
{ value: '', label: 'Select aisle…' },
|
||||
@@ -62,9 +62,6 @@ export default function Pantry() {
|
||||
setAisle('')
|
||||
showToast.success('Item added to pantry')
|
||||
},
|
||||
onError: () => {
|
||||
showToast.error('Failed to add item')
|
||||
},
|
||||
})
|
||||
|
||||
const removeMutation = useMutation({
|
||||
@@ -74,10 +71,6 @@ export default function Pantry() {
|
||||
showToast.success('Item removed')
|
||||
setRemoveId(null)
|
||||
},
|
||||
onError: () => {
|
||||
showToast.error('Failed to remove item')
|
||||
setRemoveId(null)
|
||||
},
|
||||
})
|
||||
|
||||
async function handleRemove(item: HomePantryItem) {
|
||||
@@ -100,13 +93,13 @@ export default function Pantry() {
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ['pantry'] })
|
||||
showToast.success('Item restored')
|
||||
} catch {
|
||||
showToast.error('Failed to restore item')
|
||||
} catch (err) {
|
||||
showApiError(err, 'Failed to restore item')
|
||||
}
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
showToast.error('Failed to remove item')
|
||||
// Error toast fires from the global MutationCache handler.
|
||||
} finally {
|
||||
setRemoveId(null)
|
||||
}
|
||||
@@ -133,7 +126,7 @@ export default function Pantry() {
|
||||
// refresh ingredient list so next add sees it
|
||||
await queryClient.invalidateQueries({ queryKey: ['ingredients'] })
|
||||
} catch {
|
||||
showToast.error('Failed to create ingredient')
|
||||
// Error toast fires from the global MutationCache handler.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user