Files
thrilltrack-explorer/docs/FORM_SUBMISSION_PATTERNS.md
gpt-engineer-app[bot] 700c29c910 Fix form submission toasts
2025-11-03 16:48:03 +00:00

6.8 KiB

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

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)

// 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

// ❌ 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

// ❌ 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

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

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:

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:

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
  • 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