diff --git a/docs/FORM_SUBMISSION_PATTERNS.md b/docs/FORM_SUBMISSION_PATTERNS.md new file mode 100644 index 00000000..a155b063 --- /dev/null +++ b/docs/FORM_SUBMISSION_PATTERNS.md @@ -0,0 +1,247 @@ +# 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 + +### Form-Level Errors +Forms use `handleError` utility for errors: +```typescript +try { + await onSubmit(data); +} catch (error: unknown) { + handleError(error, { + action: 'Create Park', + userId: user?.id, + metadata: { parkName: data.name } + }); +} +``` + +### Parent-Level Errors +Parent pages can catch errors for additional handling: +```typescript +try { + await submitEntityCreation(data, user.id); + toast({ title: "Success" }); + setIsModalOpen(false); +} catch (error) { + // Form already showed error toast + // Parent can log or handle specific cases + if (error.code === 'DUPLICATE_SLUG') { + // Handle specific 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 diff --git a/src/components/admin/ParkForm.tsx b/src/components/admin/ParkForm.tsx index fd9c4b7c..7afcd157 100644 --- a/src/components/admin/ParkForm.tsx +++ b/src/components/admin/ParkForm.tsx @@ -261,12 +261,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }: _compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined }); - toast({ - title: isEditing ? "Park Updated" : "Park Created", - description: isEditing - ? "The park information has been updated successfully." - : "The new park has been created successfully." - }); + // Parent component handles success feedback } catch (error: unknown) { const errorMessage = getErrorMessage(error); handleError(error, {