"use client"; import { useMemo, useState } from "react"; import type { DraftSummary } from "@pipeline/shared"; import { ApiError, assembleDraft, updateDraft } from "@/shared/pipeline-api"; import { compareDraftVersions, parseDraftPreview, selectDraftById, } from "./model"; type DraftEditorPanelProps = { articleId: string; initialDrafts: readonly DraftSummary[]; }; type DraftEditorState = { title: string; metaTitle: string; metaDescription: string; bodyMarkdown: string; }; export function DraftEditorPanel({ articleId, initialDrafts }: DraftEditorPanelProps) { const [drafts, setDrafts] = useState(() => [...initialDrafts]); const [selectedDraftId, setSelectedDraftId] = useState( initialDrafts[0]?.id ?? null, ); const [compareDraftId, setCompareDraftId] = useState(null); const [message, setMessage] = useState(""); const [isBusy, setIsBusy] = useState(false); const [editor, setEditor] = useState(() => createEditorState(initialDrafts[0] ?? null), ); const selectedDraft = useMemo( () => selectDraftById(drafts, selectedDraftId), [drafts, selectedDraftId], ); const compareDraft = useMemo( () => selectDraftById(drafts, compareDraftId), [drafts, compareDraftId], ); const previewBlocks = useMemo( () => parseDraftPreview(editor.bodyMarkdown, selectedDraft?.faq_items ?? []), [editor.bodyMarkdown, selectedDraft], ); const comparison = useMemo(() => { if (!selectedDraft || !compareDraft || selectedDraft.id === compareDraft.id) { return null; } return compareDraftVersions(selectedDraft, compareDraft); }, [selectedDraft, compareDraft]); function onSelectDraft(draftId: string) { setSelectedDraftId(draftId); setCompareDraftId((current) => (current === draftId ? null : current)); const nextDraft = selectDraftById(drafts, draftId); setEditor(createEditorState(nextDraft)); } async function onAssembleDraft() { setIsBusy(true); setMessage(""); try { const response = await assembleDraft(articleId); setDrafts((current) => [response.draft, ...current]); setSelectedDraftId(response.draft.id); setEditor(createEditorState(response.draft)); setMessage(`Draft v${response.draft.version} assembled.`); } catch (error) { setMessage(error instanceof ApiError ? error.message : "Failed to assemble draft."); } finally { setIsBusy(false); } } async function onSaveDraft() { if (!selectedDraft) { setMessage("Assemble draft before editing."); return; } setIsBusy(true); setMessage(""); try { const response = await updateDraft(articleId, selectedDraft.id, { title: editor.title, meta_title: editor.metaTitle, meta_description: editor.metaDescription, body_markdown: editor.bodyMarkdown, }); setDrafts((current) => [response.draft, ...current]); setSelectedDraftId(response.draft.id); setEditor(createEditorState(response.draft)); setMessage(`Saved draft v${response.draft.version}.`); } catch (error) { setMessage(error instanceof ApiError ? error.message : "Failed to save draft."); } finally { setIsBusy(false); } } return (

Draft editor

{drafts.length ? `${drafts.length} version(s)` : "No drafts yet"}

Selected draft
Compare with
{comparison ? (

Compare v{comparison.leftVersion} vs v{comparison.rightVersion}: changed lines{" "} {comparison.changedLineCount}, title {comparison.titleChanged ? "changed" : "same"}, meta title {comparison.metaTitleChanged ? "changed" : "same"}, description{" "} {comparison.metaDescriptionChanged ? "changed" : "same"}.

) : null} {message ?

{message}

: null}
{ event.preventDefault(); void onSaveDraft(); }} > setEditor((current) => ({ ...current, title: event.target.value })) } /> setEditor((current) => ({ ...current, metaTitle: event.target.value })) } /> setEditor((current) => ({ ...current, metaDescription: event.target.value })) } />