> ## 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.

# Form Management Dashboard

> Manage all your forms from a centralized dashboard with publishing controls and deletion

The Form Management Dashboard is your control center for viewing, publishing, and managing all forms you've created. Every form is stored securely in PostgreSQL and accessible only to its creator.

## Dashboard Overview

The dashboard displays all your forms with key metrics and quick actions.

<img src="https://mintlify.s3.us-west-1.amazonaws.com/divyang-chhantbar-fastforms-2/images/dashboard-preview.png" alt="Dashboard showing list of forms" />

### What You See

Each form card displays:

* **Form title** - The AI-generated or custom title
* **Response count** - Total number of submissions received
* **Creation date** - When the form was generated
* **Delete button** - Remove the form permanently

```typescript theme={null}
// app/dashboard/page.tsx:87-106
{forms.map((form) => (
  <div
    key={form.id}
    onClick={() => router.push(`/forms/${form.id}/responses`)}
    className="p-4 border rounded-lg hover:bg-gray-50 cursor-pointer"
  >
    <h2 className="text-xl font-semibold">{form.title}</h2>
    <p className="text-sm text-gray-600">
      {form._count.responses} responses • Created{" "}
      {new Date(form.createdAt).toLocaleDateString()}
    </p>
    <button
      onClick={(e) => handleDelete(form.id, e)}
      disabled={deletingFormId === form.id}
      className="mt-2 text-sm text-red-500 hover:underline"
    >
      {deletingFormId === form.id ? "Deleting..." : "Delete"}
    </button>
  </div>
))}
```

## Data Fetching

Forms are fetched from the `/api/getAllForms` endpoint on page load:

<Steps>
  <Step title="Component mounts">
    The `useEffect` hook triggers on component mount.
  </Step>

  <Step title="API request">
    Axios fetches all forms for the authenticated user.

    ```typescript theme={null}
    // app/dashboard/page.tsx:25-28
    const fetchForms = async () => {
      const response = await axios.get("/api/getAllForms");
      setForms(response.data.forms);
      setIsLoading(false);
    };
    ```
  </Step>

  <Step title="State update">
    Forms are stored in React state and rendered as cards.
  </Step>
</Steps>

<Note>
  The dashboard uses **optimistic UI updates** - when you delete a form, it's immediately removed from the view while the API request processes in the background.
</Note>

## Publishing & Unpublishing Forms

Forms must be **published** before they can receive responses. This gives you control over when forms go live.

### Form States

<Tabs>
  <Tab title="Unpublished">
    **Default state** when a form is first generated.

    * Cannot receive public submissions
    * Shareable link is not active
    * Shows "Publish Form" button

    ```typescript theme={null}
    // app/forms/[formId]/page.tsx:233-247
    <div>
      <p className="text-sm text-gray-600 mb-3">
        This form is not published yet. Publish it to get a shareable link.
      </p>
      <button
        type="button"
        onClick={handlePublishToggle}
        disabled={isToggling}
        className="w-full px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50"
      >
        {isToggling ? "Publishing..." : "Publish Form"}
      </button>
    </div>
    ```
  </Tab>

  <Tab title="Published">
    **Active state** - form is live and accepting responses.

    * Public URL is active: `/f/{slug}`
    * Shows shareable link with copy button
    * Shows "Unpublish Form" button

    ```typescript theme={null}
    // app/forms/[formId]/page.tsx:204-231
    <div>
      <p className="text-sm text-gray-600 mb-2">Share this form:</p>
      <div className="flex gap-2 mb-3">
        <input
          type="text"
          value={`${window.location.origin}/f/${formData.slug}`}
          readOnly
          className="flex-1 px-3 py-2 border rounded bg-white"
        />
        <button
          type="button"
          onClick={handleCopyLink}
          className="px-4 py-2 bg-black text-white rounded hover:bg-gray-800"
        >
          {isCopied ? "Copied!" : "Copy Link"}
        </button>
      </div>
      <button
        type="button"
        onClick={handlePublishToggle}
        disabled={isToggling}
        className="w-full px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50"
      >
        {isToggling ? "Unpublishing..." : "Unpublish Form"}
      </button>
    </div>
    ```
  </Tab>
</Tabs>

### Publishing Flow

<Steps>
  <Step title="Click Publish/Unpublish button">
    User clicks the toggle button in the form preview.
  </Step>

  <Step title="API request">
    A PATCH request is sent to `/api/forms/{formId}` to toggle the `isPublished` field.

    ```typescript theme={null}
    // app/forms/[formId]/page.tsx:156-166
    const handlePublishToggle = async () => {
      setIsToggling(true);
      try {
        const response = await axios.patch(`/api/forms/${formId}`);
        setFormData(response.data.data);
      } catch (error) {
        console.error("Failed to toggle publish:", error);
      } finally {
        setIsToggling(false);
      }
    };
    ```
  </Step>

  <Step title="Database update">
    The form's `isPublished` field is toggled in PostgreSQL via Prisma.
  </Step>

  <Step title="UI update">
    The component state is updated to reflect the new publish status, showing/hiding the shareable link.
  </Step>
</Steps>

<Warning>
  Unpublishing a form immediately prevents new submissions, but existing responses are preserved. The public URL will return an error if accessed.
</Warning>

## Form Preview

Each form has a preview page at `/forms/{formId}` where you can:

* See exactly how the form will appear to respondents
* All fields are **disabled** (preview mode)
* Toggle publish status
* Copy the shareable link (when published)

```typescript theme={null}
// app/forms/[formId]/page.tsx:179-187
<div className="mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
  <p className="text-sm text-blue-800">
    👁️ <strong>Preview Mode</strong> - This is how your form will look.
    Fields are disabled.
  </p>
</div>
```

<Tip>
  The preview uses the same rendering logic as the public form, ensuring WYSIWYG (What You See Is What You Get) accuracy.
</Tip>

## Form Deletion

Deleting a form permanently removes it and all associated responses.

### Deletion Flow

<Steps>
  <Step title="Click Delete button">
    User clicks the red "Delete" button on a form card.
  </Step>

  <Step title="Confirmation dialog">
    Browser confirmation dialog prevents accidental deletion.

    ```typescript theme={null}
    // app/dashboard/page.tsx:47-53
    if (
      !window.confirm(
        "Are you sure you want to delete this form? This action cannot be undone.",
      )
    ) {
      return;
    }
    ```
  </Step>

  <Step title="API request">
    DELETE request sent to `/api/forms/{formId}`.

    ```typescript theme={null}
    // app/dashboard/page.tsx:54-58
    setDeletingFormId(formId);
    const response = await axios.delete(`/api/forms/${formId}`);
    if (response.data.success) {
      setForms((prevForms) => prevForms.filter((form) => form.id !== formId));
    }
    ```
  </Step>

  <Step title="Database cascade">
    Prisma deletes the form and all related responses (cascade delete).
  </Step>

  <Step title="UI update">
    The form card is immediately removed from the dashboard via optimistic update.
  </Step>
</Steps>

<Warning>
  **Deletion is permanent and cannot be undone.** All form data and responses are permanently removed from the database.
</Warning>

## Database Schema

Forms are stored in PostgreSQL using Prisma ORM:

```prisma theme={null}
model Forms {
  id          String   @id @default(cuid())
  userId      String   // Clerk user ID
  title       String
  slug        String   @unique
  fields      Json     // Array of field objects
  isPublished Boolean  @default(false)
  createdAt   DateTime @default(now())
  responses   FormsResponses[]
}
```

### Key Fields

<Accordion title="id (String)">
  Primary key using CUID (Collision-resistant Unique Identifier) for secure, unpredictable IDs.
</Accordion>

<Accordion title="userId (String)">
  Links the form to its creator via Clerk authentication. Used for authorization checks.
</Accordion>

<Accordion title="slug (String)">
  Unique, URL-friendly identifier used in public form URLs (`/f/{slug}`). Auto-generated on creation.
</Accordion>

<Accordion title="fields (Json)">
  JSON column storing the complete form schema as an array of field objects. Allows flexible field types without schema migrations.
</Accordion>

<Accordion title="isPublished (Boolean)">
  Controls whether the form is accessible via public URL. Defaults to `false` for safety.
</Accordion>

<Accordion title="responses (FormsResponses[])">
  Relation to the responses table. Enables cascade deletion when a form is removed.
</Accordion>

## Error Handling

The dashboard handles various error states:

```typescript theme={null}
// app/dashboard/page.tsx:30-39
catch (error) {
  if (axios.isAxiosError(error)) {
    setError(error.response?.data?.error || "Failed to fetch forms");
  } else if (error instanceof Error) {
    setError(error.message);
  } else {
    setError("An unexpected error occurred");
  }
}
```

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

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

  <Tab title="Empty State">
    ```typescript theme={null}
    {forms.length === 0 ? (
      <p>No forms yet. Create one!</p>
    ) : (
      // Render form cards
    )}
    ```
  </Tab>
</Tabs>

## Navigation

Clicking a form card navigates to its responses page:

```typescript theme={null}
// app/dashboard/page.tsx:90
onClick={() => router.push(`/forms/${form.id}/responses`)}
```

<Note>
  The delete button uses `e.stopPropagation()` to prevent triggering navigation when clicking delete.
</Note>
