Fix form submission toasts

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 16:48:03 +00:00
parent 67a96ca94b
commit 700c29c910
2 changed files with 248 additions and 6 deletions

View File

@@ -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 (
<form onSubmit={handleSubmit(async (data) => {
if (!user) {
toast.error('You must be logged in to submit');
return;
}
try {
await onSubmit(data);
// ⚠️ NO SUCCESS TOAST HERE - parent handles it
// Exception: Standalone forms not in modals can show toast
} catch (error: unknown) {
handleError(error, {
action: initialData?.id ? 'Update Entity' : 'Create Entity',
metadata: { entityName: data.name }
});
}
})}>
{/* Form fields */}
</form>
);
}
```
## 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 (
<>
<Button onClick={() => setIsModalOpen(true)}>
Add Entity
</Button>
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<EntityForm
onSubmit={handleEntitySubmit}
onCancel={() => setIsModalOpen(false)}
/>
</Dialog>
</>
);
}
```
## 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

View File

@@ -261,12 +261,7 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
_compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined _compositeSubmission: (tempNewOperator || tempNewPropertyOwner) ? submissionContent : undefined
}); });
toast({ // Parent component handles success feedback
title: isEditing ? "Park Updated" : "Park Created",
description: isEditing
? "The park information has been updated successfully."
: "The new park has been created successfully."
});
} catch (error: unknown) { } catch (error: unknown) {
const errorMessage = getErrorMessage(error); const errorMessage = getErrorMessage(error);
handleError(error, { handleError(error, {