> ## Documentation Index
> Fetch the complete documentation index at: https://divyang-chhantbar-fastforms-2.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Response Collection

> Collect form submissions via unique public URLs with built-in validation

Every published form gets a unique, shareable URL where users can submit responses. The system handles validation, data collection, and success messaging automatically.

## Public Form URLs

When you publish a form, it becomes accessible at a slug-based URL:

```
https://yourapp.com/f/{slug}
```

### Slug Generation

Each form is assigned a **unique slug** on creation:

* Auto-generated by Prisma
* URL-safe characters only
* Guaranteed unique across all forms
* Persists for the lifetime of the form

<Note>
  Slugs are stored in the database with a unique constraint, preventing collisions even at scale.
</Note>

## Form Rendering

The public form page dynamically renders fields based on the stored schema.

### Page Load Flow

<Steps>
  <Step title="Extract slug from URL">
    Next.js dynamic routing captures the slug from the URL path.

    ```typescript theme={null}
    // app/f/[slug]/page.tsx:145-146
    const params = useParams();
    const slug = params.slug as string;
    ```
  </Step>

  <Step title="Fetch form data">
    The component fetches form configuration from the API using the slug.

    ```typescript theme={null}
    // app/f/[slug]/page.tsx:148-161
    const fetchFormData = async () => {
      try {
        const response = await axios.get(`/api/forms/${slug}`);
        const { data } = response.data;
        setFormData(data);
        setIsLoading(false);
      } catch (error: any) {
        setError(error.message);
        setIsLoading(false);
      }
    };
    fetchFormData();
    ```
  </Step>

  <Step title="Render form fields">
    Fields are dynamically rendered based on their type using the `renderField` function.

    ```typescript theme={null}
    // app/f/[slug]/page.tsx:219-229
    <form onSubmit={handleSubmit} className="space-y-6">
      {formData.fields.map((field: Field) => (
        <div key={field.id}>
          <label className="block mb-2 font-medium">
            {field.label}
            {field.required && <span className="text-red-500"> *</span>}
          </label>
          {renderField(field, formValues[field.id], handleFieldChange)}
        </div>
      ))}
    </form>
    ```
  </Step>
</Steps>

## Field Rendering

Each field type has specialized rendering logic:

<Tabs>
  <Tab title="Text & Email">
    ```typescript theme={null}
    // app/f/[slug]/page.tsx:32-44
    case "text":
    case "email":
      return (
        <input
          type={field.type}
          name={field.id}
          placeholder={field.placeholder}
          required={field.required}
          value={value || ""}
          onChange={(e) => onChange(field.id, e.target.value)}
          className="w-full border rounded px-3 py-2"
        />
      );
    ```
  </Tab>

  <Tab title="Select Dropdown">
    ```typescript theme={null}
    // app/f/[slug]/page.tsx:58-76
    case "select":
      return (
        <select
          name={field.id}
          required={field.required}
          value={value || ""}
          onChange={(e) => onChange(field.id, e.target.value)}
          className="w-full border rounded px-3 py-2"
        >
          <option value="" className="text-gray-700">
            Select an option
          </option>
          {field.options?.map((option: string) => (
            <option key={option} value={option}>
              {option}
            </option>
          ))}
        </select>
      );
    ```
  </Tab>

  <Tab title="Radio Buttons">
    ```typescript theme={null}
    // app/f/[slug]/page.tsx:78-95
    case "radio":
      return (
        <div className="space-y-2">
          {field.options?.map((option: string) => (
            <label key={option} className="flex items-center gap-2">
              <input
                type="radio"
                name={field.id}
                value={option}
                required={field.required}
                checked={value === option}
                onChange={(e) => onChange(field.id, e.target.value)}
              />
              <span>{option}</span>
            </label>
          ))}
        </div>
      );
    ```
  </Tab>

  <Tab title="Checkboxes">
    ```typescript theme={null}
    // app/f/[slug]/page.tsx:97-118
    case "checkbox":
      return (
        <div className="space-y-2">
          {field.options?.map((option: string) => (
            <label key={option} className="flex items-center gap-2">
              <input
                type="checkbox"
                value={option}
                checked={value?.includes(option) || false}
                onChange={(e) => {
                  const currentValues = value || [];
                  const newValues = e.target.checked
                    ? [...currentValues, option]
                    : currentValues.filter((v: string) => v !== option);
                  onChange(field.id, newValues);
                }}
              />
              <span>{option}</span>
            </label>
          ))}
        </div>
      );
    ```
  </Tab>
</Tabs>

## Form State Management

Form values are tracked in React state:

```typescript theme={null}
// app/f/[slug]/page.tsx:141
const [formValues, setFormValues] = useState<Record<string, any>>({});
```

### Value Updates

Each field change updates the centralized state object:

```typescript theme={null}
// app/f/[slug]/page.tsx:163-168
const handleFieldChange = (fieldId: string, value: any) => {
  setFormValues((prev) => ({
    ...prev,
    [fieldId]: value,
  }));
};
```

<Tip>
  Using a single state object with field IDs as keys makes it easy to construct the submission payload.
</Tip>

## Form Submission

When the user clicks Submit, the form data is validated and sent to the API.

<Steps>
  <Step title="User clicks Submit">
    The form's `onSubmit` handler is triggered.
  </Step>

  <Step title="Browser validation">
    HTML5 validation runs automatically for required fields and input types (email, number, etc.).
  </Step>

  <Step title="Prevent default & submit">
    The handler prevents default form behavior and sends an API request.

    ```typescript theme={null}
    // app/f/[slug]/page.tsx:170-188
    const handleSubmit = async (e: React.FormEvent) => {
      e.preventDefault();
      setIsSubmitting(true);

      try {
        await axios.post("/api/forms/submit", {
          formId: formData?.id,
          data: formValues,
        });

        setSubmitSuccess(true);
        setFormValues({});
      } catch (error: any) {
        console.error("Form submission failed:", error);
        setError("Failed to submit form. Please try again.");
      } finally {
        setIsSubmitting(false);
      }
    };
    ```
  </Step>

  <Step title="Server-side validation">
    The API validates the submission (see next section).
  </Step>

  <Step title="Success message">
    On success, a thank you message is displayed.

    ```typescript theme={null}
    // app/f/[slug]/page.tsx:202-213
    if (submitSuccess) {
      return (
        <div className="min-h-screen p-8 max-w-xl mx-auto flex items-center justify-center">
          <div className="text-center">
            <h2 className="text-2xl font-bold mb-4">Thank you!</h2>
            <p className="text-gray-600">
              Your response has been submitted successfully.
            </p>
          </div>
        </div>
      );
    }
    ```
  </Step>
</Steps>

## Validation

Multiple layers of validation ensure data integrity.

### Client-Side Validation

**HTML5 validation** runs automatically:

* `required` attribute for mandatory fields
* `type="email"` for email format
* `type="number"` for numeric values

```typescript theme={null}
// app/f/[slug]/page.tsx:36
required={field.required}
```

### Server-Side Validation

The API performs comprehensive validation before storing responses.

<Steps>
  <Step title="Check form ID">
    ```typescript theme={null}
    // app/api/forms/submit/route.ts:11-16
    if (!formId || formId.trim().length === 0) {
      return NextResponse.json(
        { error: "Form ID is required and cannot be empty" },
        { status: 400 },
      );
    }
    ```
  </Step>

  <Step title="Validate data object">
    ```typescript theme={null}
    // app/api/forms/submit/route.ts:18-23
    if (!data || typeof data !== "object") {
      return NextResponse.json(
        { error: "Form data is required and must be an object" },
        { status: 400 },
      );
    }
    ```
  </Step>

  <Step title="Verify form exists">
    ```typescript theme={null}
    // app/api/forms/submit/route.ts:25-31
    const form = await prisma.forms.findUnique({
      where: { id: formId },
    });

    if (!form) {
      return NextResponse.json({ error: "Form not found" }, { status: 404 });
    }
    ```
  </Step>

  <Step title="Validate required fields">
    ```typescript theme={null}
    // app/api/forms/submit/route.ts:42-49
    for (const field of formFields) {
      if (field.required && !submittedKeys.includes(field.id)) {
        return NextResponse.json(
          { error: `Required field missing: ${field.label}` },
          { status: 400 },
        );
      }
    }
    ```
  </Step>

  <Step title="Store response">
    ```typescript theme={null}
    // app/api/forms/submit/route.ts:51-56
    const formResponse = await prisma.formsResponses.create({
      data: {
        formId: formId,
        data: data,
      },
    });
    ```
  </Step>
</Steps>

<Warning>
  The API returns specific error messages for validation failures, helping users understand what went wrong.
</Warning>

## Response Storage

Responses are stored in the `FormsResponses` table:

```prisma theme={null}
model FormsResponses {
  id        String   @id @default(cuid())
  formId    String
  data      Json     // User's submitted data
  createdAt DateTime @default(now())
  form      Forms    @relation(fields: [formId], references: [id], onDelete: Cascade)
}
```

### Key Features

<Accordion title="JSON Storage">
  The `data` field stores the entire submission as JSON, allowing flexible schemas without migrations.

  Example stored data:

  ```json theme={null}
  {
    "full_name": "John Doe",
    "email": "john@example.com",
    "rating": "Very Satisfied",
    "comments": "Great service!"
  }
  ```
</Accordion>

<Accordion title="Cascade Deletion">
  When a form is deleted, all its responses are automatically removed via `onDelete: Cascade`.
</Accordion>

<Accordion title="Timestamp Tracking">
  `createdAt` automatically records when each response was submitted for analytics and exports.
</Accordion>

## Error States

The public form page handles various error scenarios:

<Tabs>
  <Tab title="Loading">
    ```typescript theme={null}
    if (isLoading) {
      return <div className="p-8">Loading form...</div>;
    }
    ```
  </Tab>

  <Tab title="Form Not Found">
    ```typescript theme={null}
    if (!formData) {
      return <div className="p-8">Form not found</div>;
    }
    ```
  </Tab>

  <Tab title="API Error">
    ```typescript theme={null}
    if (error) {
      return <div className="p-8 text-red-500">{error}</div>;
    }
    ```
  </Tab>

  <Tab title="Submission Failed">
    ```typescript theme={null}
    catch (error: any) {
      console.error("Form submission failed:", error);
      setError("Failed to submit form. Please try again.");
    }
    ```
  </Tab>
</Tabs>

## UX Features

### Loading States

The submit button shows loading state during submission:

```typescript theme={null}
// app/f/[slug]/page.tsx:231-237
<button
  type="submit"
  disabled={isSubmitting}
  className="w-full mt-4 px-4 py-2 bg-black text-white rounded hover:bg-gray-800 disabled:opacity-50"
>
  {isSubmitting ? "Submitting..." : "Submit"}
</button>
```

### Required Field Indicators

Required fields are clearly marked with a red asterisk:

```typescript theme={null}
// app/f/[slug]/page.tsx:222-225
<label className="block mb-2 font-medium">
  {field.label}
  {field.required && <span className="text-red-500"> *</span>}
</label>
```

<Tip>
  The combination of visual indicators and browser validation provides clear feedback before users submit.
</Tip>

## API Endpoint

**POST** `/api/forms/submit`

**Request Body:**

```json theme={null}
{
  "formId": "cm123abc",
  "data": {
    "full_name": "John Doe",
    "email": "john@example.com",
    "rating": "Very Satisfied"
  }
}
```

**Success Response:**

```json theme={null}
{
  "success": true,
  "message": "Response submitted successfully",
  "responseId": "cm456def"
}
```

**Error Response:**

```json theme={null}
{
  "error": "Required field missing: Email Address"
}
```

**Implementation:** `app/api/forms/submit/route.ts`
