"use client"; /** * FormView — the database's FORM-view surface (the ViewRouter's `case "form"`). * * A FORM view links to a single form via `view.config.formId`. This panel: * - with no form yet → an empty state: "Create form" (POST * /databases/:id/forms) + "choose an existing form" when the database has * any. Creating/choosing links it by PATCHing the view's `config.formId`. * - with a form → a Builder | Responses tab bar + a public-link chip * (/f/, copy). Builder = ; Responses = * (TASK-0109). * * The in-app FORM view IS the workspace experience for forms (the public * /f/ page is the separate, auth-less submission surface, TASK-0107). */ import { useState } from "react"; import { ClipboardCopy, ClipboardList, Inbox, Plus } from "lucide-react"; import { toast } from "@/stores/ui-store"; import { DatabaseProperty, DatabaseView, RecordDto, useUpdateView } from "@/hooks/use-database"; import { cn } from "@/lib/utils"; import { useCreateForm, useForm, useListForms } from "@/hooks/use-forms"; import { FormBuilderView } from "@/components/database/form-builder-view"; import { FormResponsesView } from "@/components/form/form-responses-view"; type FormTab = "builder" | "responses"; export interface FormViewProps { databaseId: string; workspaceId?: string; view: DatabaseView; properties: DatabaseProperty[]; readOnly?: boolean; onOpenRecord?: (record: RecordDto) => void; onOpenRecordPage?: (record: RecordDto) => void; } export function FormView({ databaseId, workspaceId, view, properties, readOnly, onOpenRecord, onOpenRecordPage }: FormViewProps) { const formId = (view.config.formId as string | undefined) ?? undefined; const { data: form, isLoading: formLoading } = useForm(formId); const [tab, setTab] = useState("builder"); const [newName, setNewName] = useState(""); const createForm = useCreateForm(databaseId); const updateView = useUpdateView(databaseId); const { data: existingForms } = useListForms(databaseId); const linkForm = (id: string) => updateView.mutate( { viewId: view.id, config: { ...(view.config ?? {}), formId: id } }, { onError: (e) => toast("error", "Could not link the form", (e as Error).message) } ); const createAndLink = () => { const name = newName.trim(); if (!name) return; createForm.mutate( { name }, { onSuccess: (created) => { setNewName(""); linkForm(created.id); toast("success", "Form created", `Now editing "${created.name}" — add questions and save to publish.`); }, onError: (e) => toast("error", "Create failed", (e as Error).message) } ); }; const copyLink = async () => { if (!form) return; const url = `${window.location.origin}/f/${form.slug}`; try { await navigator.clipboard.writeText(url); toast("success", "Public link copied", url); } catch { toast("error", "Could not copy the link", url); } }; // ---- no form yet ---------------------------------------------------------- if (!formId) { return (

This form view has no linked form yet.

{!readOnly && (
setNewName(e.target.value)} onKeyDown={(e) => e.key === "Enter" && createAndLink()} placeholder="Form name, e.g. Feedback" aria-label="New form name" className="w-56 rounded-md border border-input bg-background px-2.5 py-1.5 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring" />
{existingForms && existingForms.length > 0 && (
or link an existing form:
)}
)}
); } // ---- form linked: tabs + link chip --------------------------------------- return (
setTab("builder")} label="Builder" testid="form-view-tab-builder" /> setTab("responses")} label="Responses" testid="form-view-tab-responses" /> {form && (
/f/{form.slug}
)}
{formLoading || !form ? (
Loading form…
) : tab === "builder" ? ( ) : ( )}
); } function TabButton({ active, onClick, label, testid }: { active: boolean; onClick: () => void; label: string; testid: string; }) { return ( ); }