From ef7332b72235b196b98792039310072528224643 Mon Sep 17 00:00:00 2001 From: arigatoexpress <95630102+arigatoexpress@users.noreply.github.com> Date: Fri, 19 Jun 2026 18:48:43 -0600 Subject: [PATCH] fix(frontend): inventory retry, slots crash-guard, honest report-issue (P3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Holistic review, P3 UX/robustness on customer-facing pages. - InventoryBrowse: fetchInventory never cleared a prior error, so 'Try Again' stayed stuck on the error screen even after a successful retry. Clear error at the start of each (re)fetch. - Appointments: a malformed 200 (missing / non-array available_slots) crashed the render at .length / .map. Normalize available_slots to an array on set. - ReportIssue: ignored resp.ok and always showed a success checkmark, so a 4xx/5xx silently dropped the report. Now checks resp.ok and shows an honest 'couldn't send — try again / call us' state. + vitest regression tests. Frontend build + eslint clean; 50 vitest pass. Co-Authored-By: Claude Opus 4.8 --- frontend/src/__tests__/ReportIssue.test.jsx | 40 +++++++++++++++++++++ frontend/src/components/ReportIssue.jsx | 30 ++++++++++++---- frontend/src/pages/Appointments.jsx | 7 +++- frontend/src/pages/InventoryBrowse.jsx | 1 + 4 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 frontend/src/__tests__/ReportIssue.test.jsx diff --git a/frontend/src/__tests__/ReportIssue.test.jsx b/frontend/src/__tests__/ReportIssue.test.jsx new file mode 100644 index 0000000..0e368c8 --- /dev/null +++ b/frontend/src/__tests__/ReportIssue.test.jsx @@ -0,0 +1,40 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import ReportIssue from '../components/ReportIssue'; + +// Regression guard: the report widget used to ignore resp.ok and always show a +// success checkmark — so a failed POST (4xx/5xx) silently dropped the report. + +describe('ReportIssue', () => { + beforeEach(() => { + global.fetch = vi.fn(); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + function openAndSend(text) { + fireEvent.click(screen.getByTitle('Report an issue')); + fireEvent.change(screen.getByPlaceholderText(/Describe what happened/i), { + target: { value: text }, + }); + fireEvent.click(screen.getByRole('button', { name: /Send Report/i })); + } + + it('shows a failure message (NOT success) when the backend POST fails', async () => { + global.fetch.mockResolvedValue({ ok: false }); + render(); + openAndSend('Inventory page is blank'); + + expect(await screen.findByText(/Couldn't send your report/i)).toBeInTheDocument(); + expect(screen.queryByText(/We'll look into it/i)).not.toBeInTheDocument(); + }); + + it('shows success when the report actually sends', async () => { + global.fetch.mockResolvedValue({ ok: true }); + render(); + openAndSend('Just a note'); + + expect(await screen.findByText(/We'll look into it/i)).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/components/ReportIssue.jsx b/frontend/src/components/ReportIssue.jsx index fa6f39e..bc9e87c 100644 --- a/frontend/src/components/ReportIssue.jsx +++ b/frontend/src/components/ReportIssue.jsx @@ -10,6 +10,7 @@ export default function ReportIssue() { const [description, setDescription] = useState(''); const [sent, setSent] = useState(false); const [sending, setSending] = useState(false); + const [failed, setFailed] = useState(false); const submit = async () => { if (!description.trim()) return; @@ -24,21 +25,27 @@ export default function ReportIssue() { timestamp: new Date().toISOString(), }; + let ok = false; try { - // Try to send to backend (which will log + optionally notify) - await fetch('/api/feedback', { + const resp = await fetch('/api/feedback', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(report), }); + ok = resp.ok; // a 4xx/5xx is NOT a successful submission } catch { - // Even if backend fails, log locally - console.log('Issue report:', report); + ok = false; } setSending(false); - setSent(true); - setTimeout(() => { setSent(false); setOpen(false); setDescription(''); }, 2000); + if (ok) { + setSent(true); + setTimeout(() => { setSent(false); setOpen(false); setDescription(''); }, 2000); + } else { + // Don't falsely confirm — let the customer retry or call instead. + console.warn('Issue report failed to send:', report); + setFailed(true); + } }; if (!open) { @@ -69,6 +76,17 @@ export default function ReportIssue() { Thank you! We'll look into it. + ) : failed ? ( +
+

Couldn't send your report.

+

Please try again, or call our showroom and we'll help right away.

+ +
) : ( <>