"use client"; import { useMemo, useState } from "react"; import type { FinalApprovalChecklistResponse } from "@pipeline/shared"; import { ApiError, fetchFinalApprovalChecklist, submitFinalApproval, submitFinalRevisionRequest, } from "@/shared/pipeline-api"; import { buildFinalApprovalViewModel } from "./model"; type FinalApprovalPanelProps = { articleId: string; }; const DEFAULT_FRONTMATTER = '{\n "category": "Guides"\n}'; export function FinalApprovalPanel({ articleId }: FinalApprovalPanelProps) { const [checklist, setChecklist] = useState(null); const [draftVersionInput, setDraftVersionInput] = useState(""); const [contentPath, setContentPath] = useState("/guides/final-approval-gate"); const [author, setAuthor] = useState("Editorial Team"); const [publishingMode, setPublishingMode] = useState("MANUAL"); const [frontmatterText, setFrontmatterText] = useState(DEFAULT_FRONTMATTER); const [revisionReason, setRevisionReason] = useState("Need final refinements before approval."); const [busyKey, setBusyKey] = useState(null); const [message, setMessage] = useState(""); const viewModel = useMemo( () => buildFinalApprovalViewModel(checklist), [checklist], ); async function refreshChecklist() { setBusyKey("refresh"); setMessage(""); try { const response = await fetchFinalApprovalChecklist(articleId); setChecklist(response); if (!draftVersionInput && response.draft_version) { setDraftVersionInput(String(response.draft_version)); } } catch (error) { setMessage(error instanceof ApiError ? error.message : "Failed to fetch final checklist."); } finally { setBusyKey(null); } } async function approve() { let frontmatter: Record | null = null; try { const parsed = JSON.parse(frontmatterText); if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { frontmatter = parsed as Record; } } catch { setMessage("Frontmatter must be a valid JSON object."); return; } const parsedDraftVersion = Number.parseInt(draftVersionInput, 10); setBusyKey("approve"); setMessage(""); try { const response = await submitFinalApproval(articleId, { draft_version: Number.isNaN(parsedDraftVersion) ? null : parsedDraftVersion, publishing_settings: { content_path: contentPath, author, publishing_mode: publishingMode, frontmatter, }, }); setChecklist(response.checklist); setMessage(`Final approval completed. Article status: ${response.article.status}.`); } catch (error) { setMessage(error instanceof ApiError ? error.message : "Final approval failed."); await refreshChecklist(); } finally { setBusyKey(null); } } async function requestRevision() { const parsedDraftVersion = Number.parseInt(draftVersionInput, 10); setBusyKey("revision"); setMessage(""); try { const response = await submitFinalRevisionRequest(articleId, { reason: revisionReason, draft_version: Number.isNaN(parsedDraftVersion) ? null : parsedDraftVersion, }); setChecklist(response.checklist); setMessage(`Revision requested. Article status: ${response.article.status}.`); } catch (error) { setMessage(error instanceof ApiError ? error.message : "Failed to request revision."); } finally { setBusyKey(null); } } return (

Final approval gate

Checklist blockers: {viewModel.blockers.length}

{message ?

{message}

: null} {viewModel.items.length > 0 ? ( {viewModel.items.map((item) => ( ))}
Checklist item Status Details
{item.title} {item.completed ? "Completed" : "Blocked"} {item.completed ? "—" : item.blocker || "—"}
) : (

No checklist yet. Refresh to load current gate status.

)} {viewModel.completedItems.length > 0 ? (
    {viewModel.completedItems.map((item) => (
  • Completed: {item}
  • ))}
) : null} {viewModel.blockers.length > 0 ? (
    {viewModel.blockers.map((item) => (
  • {item}
  • ))}
) : null}
setDraftVersionInput(event.target.value)} placeholder={viewModel.draftVersion ? String(viewModel.draftVersion) : "Draft version"} inputMode="numeric" /> setContentPath(event.target.value)} placeholder="/guides/final-approval-gate" /> setAuthor(event.target.value)} placeholder="Author" /> setPublishingMode(event.target.value)} placeholder="MANUAL" />