Skip to main content
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. 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
// 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:
1

Component mounts

The useEffect hook triggers on component mount.
2

API request

Axios fetches all forms for the authenticated user.
// app/dashboard/page.tsx:25-28
const fetchForms = async () => {
  const response = await axios.get("/api/getAllForms");
  setForms(response.data.forms);
  setIsLoading(false);
};
3

State update

Forms are stored in React state and rendered as cards.
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.

Publishing & Unpublishing Forms

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

Form States

Default state when a form is first generated.
  • Cannot receive public submissions
  • Shareable link is not active
  • Shows “Publish Form” button
// 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>

Publishing Flow

1

Click Publish/Unpublish button

User clicks the toggle button in the form preview.
2

API request

A PATCH request is sent to /api/forms/{formId} to toggle the isPublished field.
// 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);
  }
};
3

Database update

The form’s isPublished field is toggled in PostgreSQL via Prisma.
4

UI update

The component state is updated to reflect the new publish status, showing/hiding the shareable link.
Unpublishing a form immediately prevents new submissions, but existing responses are preserved. The public URL will return an error if accessed.

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)
// 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>
The preview uses the same rendering logic as the public form, ensuring WYSIWYG (What You See Is What You Get) accuracy.

Form Deletion

Deleting a form permanently removes it and all associated responses.

Deletion Flow

1

Click Delete button

User clicks the red “Delete” button on a form card.
2

Confirmation dialog

Browser confirmation dialog prevents accidental deletion.
// app/dashboard/page.tsx:47-53
if (
  !window.confirm(
    "Are you sure you want to delete this form? This action cannot be undone.",
  )
) {
  return;
}
3

API request

DELETE request sent to /api/forms/{formId}.
// 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));
}
4

Database cascade

Prisma deletes the form and all related responses (cascade delete).
5

UI update

The form card is immediately removed from the dashboard via optimistic update.
Deletion is permanent and cannot be undone. All form data and responses are permanently removed from the database.

Database Schema

Forms are stored in PostgreSQL using Prisma ORM:
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

Primary key using CUID (Collision-resistant Unique Identifier) for secure, unpredictable IDs.
Links the form to its creator via Clerk authentication. Used for authorization checks.
Unique, URL-friendly identifier used in public form URLs (/f/{slug}). Auto-generated on creation.
JSON column storing the complete form schema as an array of field objects. Allows flexible field types without schema migrations.
Controls whether the form is accessible via public URL. Defaults to false for safety.
Relation to the responses table. Enables cascade deletion when a form is removed.

Error Handling

The dashboard handles various error states:
// 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");
  }
}
if (isLoading) {
  return <div className="p-8">Loading...</div>;
}
Clicking a form card navigates to its responses page:
// app/dashboard/page.tsx:90
onClick={() => router.push(`/forms/${form.id}/responses`)}
The delete button uses e.stopPropagation() to prevent triggering navigation when clicking delete.