From 4ca40bf2b55cd8e643060fb5e653e5ecd31f9560 Mon Sep 17 00:00:00 2001 From: belviskhoremk Date: Wed, 12 Aug 2026 23:03:32 +0200 Subject: [PATCH] fix: address final whole-branch review findings for Phase 2C Critical: computeAvailableSlots looped forever when slot_minutes was 0 or negative -- reachable in production, since AdminSchedule's slot-length input has no validation and clearing it yields Number('') === 0. Guarded the computation, added min="5"/required to the admin input as defense in depth, and added a regression test. Important fixes: - Removed the one eslint-disable that slipped into the branch (InquiryModal.test.tsx), replacing it with the explicit vi.fn<...> generic type pattern already used everywhere else. Also cleaned up a similar unused-var workaround in useBooking.test.ts for consistency. - Added a submitting/in-flight guard to both booking and inquiry submission, so a double-click can no longer produce a false "slot already taken" error after a booking actually succeeded (or a duplicate inquiry row). - Added loading states to Home.tsx (services/products) and Book.tsx (services list, date/slot step) -- previously only Shop.tsx had one, though the spec named all three pages. - Restored the full date -> slot -> contact -> confirmation -> persisted-booking test in Book.test.tsx, which had been replaced rather than supplemented by a narrower slot-fetching test. Deferred to a follow-up (explicit decision, not silently dropped): duration-aware slot conflicts. The current model only blocks an exact (date, time) match, so a 4-hour service booked at 9:00 AM doesn't block 11:00 AM even though they'd physically overlap. Inherited from the Phase 2A data model; this branch is what makes it reachable by real customers, so it needs a recorded decision before go-live rather than silent inheritance. Claude-Session: https://claude.ai/code/session_01NhxA4npAXAxmCDEJt6kxSH --- src/components/InquiryModal.test.tsx | 3 +-- src/components/InquiryModal.tsx | 7 +++++- src/data/availability.test.ts | 6 +++++ src/data/availability.ts | 9 ++++++++ src/hooks/useBooking.test.ts | 6 +---- src/hooks/useBooking.ts | 10 +++++++-- src/pages/Book.test.tsx | 33 +++++++++++++++++++++++++++- src/pages/Book.tsx | 7 +++++- src/pages/Home.tsx | 4 ++++ src/pages/admin/AdminSchedule.tsx | 2 ++ 10 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/components/InquiryModal.test.tsx b/src/components/InquiryModal.test.tsx index f4a73e6..f8a7e0a 100644 --- a/src/components/InquiryModal.test.tsx +++ b/src/components/InquiryModal.test.tsx @@ -3,8 +3,7 @@ import userEvent from '@testing-library/user-event' import { describe, it, expect, vi, beforeEach } from 'vitest' const insertMock = vi.fn() -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const fromMock = vi.fn((_: string) => ({ insert: insertMock })) +const fromMock = vi.fn<(table: string) => { insert: typeof insertMock }>(() => ({ insert: insertMock })) vi.mock('../lib/supabase', () => ({ supabase: { from: (table: string) => fromMock(table) }, diff --git a/src/components/InquiryModal.tsx b/src/components/InquiryModal.tsx index e0e4fa4..6156567 100644 --- a/src/components/InquiryModal.tsx +++ b/src/components/InquiryModal.tsx @@ -23,6 +23,7 @@ function InquiryModal({ product, onClose }: InquiryModalProps) { const [errors, setErrors] = useState({}) const [submitError, setSubmitError] = useState('') const [submitted, setSubmitted] = useState(false) + const [submitting, setSubmitting] = useState(false) const nameInputRef = useRef(null) useEffect(() => { @@ -39,6 +40,8 @@ function InquiryModal({ product, onClose }: InquiryModalProps) { async function handleSubmit(e: FormEvent) { e.preventDefault() + if (submitting) return + const nextErrors: Errors = {} if (!name.trim()) nextErrors.name = 'Le nom est requis.' if (!isValidEmail(email)) nextErrors.email = 'Une adresse courriel valide est requise.' @@ -47,6 +50,7 @@ function InquiryModal({ product, onClose }: InquiryModalProps) { return } + setSubmitting(true) setSubmitError('') const { error } = await supabase.from('inquiries').insert({ product_id: product.id, @@ -54,6 +58,7 @@ function InquiryModal({ product, onClose }: InquiryModalProps) { contact_email: email, contact_notes: notes, }) + setSubmitting(false) if (error) { setSubmitError(error.message) @@ -104,7 +109,7 @@ function InquiryModal({ product, onClose }: InquiryModalProps) {