"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; interface DocFormProps { mode: "create" | "edit"; slug?: string; secret: string; // injected server-side (never visible to the client JS bundle) initial?: { title?: string; body?: string; section?: "docs" | "writeups" | "research" | "notes"; type?: "documentation" | "writeup" | "research" | "note"; status?: "published" | "draft"; tags?: string[]; author?: string; }; } const SECTION_OPTIONS = ["docs", "writeups", "research", "notes"] as const; const TYPE_OPTIONS = ["documentation", "writeup", "research", "note"] as const; export default function DocForm({ mode, slug, secret, initial }: DocFormProps) { const router = useRouter(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [title, setTitle] = useState(initial?.title ?? ""); const [body, setBody] = useState(initial?.body ?? ""); const [section, setSection] = useState(initial?.section ?? "docs"); const [type, setType] = useState(initial?.type ?? "documentation"); const [status, setStatus] = useState(initial?.status ?? "published"); const [tags, setTags] = useState(initial?.tags?.join(", ") ?? ""); const [author, setAuthor] = useState(initial?.author ?? ""); const tagList = tags .split(",") .map((t) => t.trim()) .filter(Boolean); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setError(null); const payload = { title, body, section, type, status, author, tags: tagList, }; try { let res: Response; if (mode === "create") { const slugVal = slug ?? title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, ""); res = await fetch("/api/docs", { method: "POST", headers: { "Content-Type": "application/json", "x-webhook-secret": secret }, body: JSON.stringify({ slug: slugVal, ...payload }), }); } else { const editSlug = slug ?? ""; res = await fetch(`/api/docs/${editSlug}`, { method: "PUT", headers: { "Content-Type": "application/json", "x-webhook-secret": secret }, body: JSON.stringify(payload), }); } if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error ?? "Save failed"); } const result = await res.json(); router.push(result.doc?.slug ? `/${result.doc.slug}` : "/"); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setLoading(false); } } return (
{error && (
{error}
)}
setTitle(e.target.value)} className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-700 rounded bg-white dark:bg-zinc-900" required />
router.replace(`/docs/${section}/edit?slug=${e.target.value}`)} className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-700 rounded bg-zinc-50 dark:bg-zinc-800" placeholder={section} />

URL-safe path under the section (e.g. "websocket/contract").

setTags(e.target.value)} className="w-full px-3 py-2 border border-zinc-300 dark:border-zinc-700 rounded bg-white dark:bg-zinc-900" placeholder="comma, separated, tags" />
setAuthor(e.target.value)} className="w-fill px-3 py-2 border border-zinc-300 dark:border-zinc-700 rounded bg-white dark:bg-zinc-900" />