# Form Submission Patterns ## Overview This document defines the standard patterns for handling form submissions, toast notifications, and modal behavior across ThrillWiki. ## Core Principles ### Separation of Concerns - **Forms** handle UI, validation, and data collection - **Parent Pages** handle submission logic and user feedback - **Submission Helpers** handle database operations ### Single Source of Truth - Only parent pages show success toasts - Forms should not assume submission outcomes - Modal closing is controlled by parent after successful submission ## Toast Notification Rules ### ✅ DO **Parent Pages Show Toasts** ```typescript const handleParkSubmit = async (data: FormData) => { try { await submitParkCreation(data, user.id); toast({ title: "Park Submitted", description: "Your submission has been sent for review." }); setIsModalOpen(false); // Close modal after success } catch (error) { // Error already handled by form via handleError utility } }; ``` **Use Correct Terminology** - ✅ "Submitted for review" (for new entities) - ✅ "Edit submitted" (for updates) - ❌ "Created" or "Updated" (implies immediate approval) **Conditional Toast in Forms (Only for standalone usage)** ```typescript // Only show toast if NOT being called from a parent handler if (!initialData?.id) { toast.success('Designer submitted for review'); onCancel(); } ``` ### ❌ DON'T **Forms Should NOT Show Success Toasts for Main Submissions** ```typescript // ❌ WRONG - Form doesn't know if submission succeeded const handleFormSubmit = async (data: FormData) => { await onSubmit(data); toast({ title: "Park Created", // ❌ Misleading terminology description: "The new park has been created successfully." }); }; ``` **Duplicate Toasts** ```typescript // ❌ WRONG - Both form and parent showing toasts // Form: toast({ title: "Park Created" }); // Parent: toast({ title: "Park Submitted" }); ``` ## Modal Behavior ### Expected Flow 1. User fills form and clicks submit 2. Form validates and calls `onSubmit` prop 3. Parent page handles submission 4. Parent shows appropriate toast 5. Parent closes modal via `setIsModalOpen(false)` ### Common Issues **Issue**: Modal doesn't close after submission **Cause**: Form is showing a toast that interferes with normal flow **Solution**: Remove form-level success toasts **Issue**: User sees "Created" but item isn't visible **Cause**: Using wrong terminology - submissions go to moderation **Solution**: Use "Submitted for review" instead of "Created" ## Form Component Template ```typescript export function EntityForm({ onSubmit, onCancel, initialData }: EntityFormProps) { const { user } = useAuth(); const { register, handleSubmit, /* ... */ } = useForm({ // ... form config }); return (
); } ``` ## Parent Page Template ```typescript export function EntityListPage() { const [isModalOpen, setIsModalOpen] = useState(false); const handleEntitySubmit = async (data: FormData) => { try { const result = await submitEntityCreation(data, user.id); // ✅ Parent shows success feedback toast({ title: "Entity Submitted", description: "Your submission has been sent for review." }); // ✅ Parent closes modal setIsModalOpen(false); // ✅ Parent refreshes data queryClient.invalidateQueries(['entities']); } catch (error) { // Form already showed error via handleError // Parent can optionally add additional handling console.error('Submission failed:', error); } }; return ( <> > ); } ``` ## Error Handling ### ⚠️ CRITICAL: Error Propagation Pattern Forms MUST re-throw errors after logging them so parent components can respond appropriately (keep modals open, show additional context, etc.). **Forms MUST re-throw errors:** ```typescript } catch (error: unknown) { // Log error for debugging and show toast to user handleError(error, { action: 'Submit Park', userId: user?.id, metadata: { parkName: data.name } }); // ⚠️ CRITICAL: Re-throw so parent can handle modal state throw error; } ``` **Why Re-throw?** - Parent needs to know submission failed - Modal should stay open so user can retry - User can fix validation issues and resubmit - Prevents "success" behavior on failures - Maintains proper error flow through the app ### Parent-Level Error Handling ```typescript const handleParkSubmit = async (data: FormData) => { try { await submitParkCreation(data, user.id); toast.success('Park submitted for review'); setIsModalOpen(false); // Only close on success } catch (error) { // Error already toasted by form via handleError() // Modal stays open automatically because we don't close it // User can fix issues and retry console.error('Submission failed:', error); } }; ``` **Expected Error Flow:** 1. User submits form → `onSubmit()` called 2. Submission fails → Form catches error 3. Form shows error toast via `handleError()` 4. Form re-throws error to parent 5. Parent's catch block executes 6. Modal stays open (no `setIsModalOpen(false)`) 7. User fixes issue and tries again **Common Mistake:** ```typescript // ❌ WRONG - Error not re-thrown, parent never knows } catch (error: unknown) { handleError(error, { action: 'Submit' }); // Missing: throw error; } ``` ## Current Implementation Status ### ✅ Correct Implementation - `DesignerForm.tsx` - Shows "Designer submitted for review" only when `!initialData?.id` - `OperatorForm.tsx` - Shows "Operator submitted for review" only when `!initialData?.id` - `PropertyOwnerForm.tsx` - Shows "Property owner submitted for review" only when `!initialData?.id` - `ManufacturerForm.tsx` - Shows "Manufacturer submitted for review" only when `!initialData?.id` - `RideModelForm.tsx` - No toasts, parent handles everything - `RideForm.tsx` - Shows "Submission Sent" with conditional description - `ParkForm.tsx` - Fixed to remove premature success toast ### Parent Pages - `Parks.tsx` - Shows "Park Submitted" ✅ - `Operators.tsx` - Shows "Operator Submitted" ✅ - `Designers.tsx` - Shows "Designer Submitted" ✅ - `Manufacturers.tsx` - Shows "Manufacturer Submitted" ✅ - `ParkDetail.tsx` - Shows "Submission Sent" ✅ ## Testing Checklist When implementing or updating a form: - [ ] Form validates input correctly - [ ] Form calls `onSubmit` prop with clean data - [ ] Form only shows error toasts, not success toasts (unless standalone) - [ ] Parent page shows appropriate success toast - [ ] Success toast uses correct terminology ("submitted" not "created") - [ ] Modal closes after successful submission - [ ] User sees single toast, not duplicates - [ ] Error handling provides actionable feedback - [ ] Form can be used both in modals and standalone ## Related Files - `src/lib/errorHandler.ts` - Error handling utilities - `src/lib/entitySubmissionHelpers.ts` - Submission logic - `src/hooks/use-toast.ts` - Toast notification hook - `tests/e2e/submission/park-creation.spec.ts` - E2E tests for submission flow