import React, { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { Modal } from '../../ui/Modal'; import { AssetPicker } from '../../ui/AssetPicker'; import { PageData, PageSeo } from '../../types'; interface PageSettingsModalProps { open: boolean; onClose: () => void; page: PageData | null; updatePageSeo: (pageId: string, seo: Partial) => void; } const EMPTY_SEO: PageSeo = { metaTitle: '', metaDescription: '', ogTitle: '', ogImage: '', twitterCard: 'summary_large_image', noindex: false, }; /** * Per-page SEO/meta settings (PKG-H contract §1/§4). Opened via the gear * icon on a page row in PagesPanel. Fields map 1:1 onto `PageData.seo` * (`types/index.ts`) and are consumed by `html-export.ts`'s `wrapInDocument` * (Preview) and the backend's `generateCompiledHTML` (published output) -- * field names here MUST match the shared contract exactly. * * Follows the HeadCodeModal precedent: portaled to `document.body` (escapes * the topbar/left-panel's own stacking context, same fix HeadCodeModal and * TemplateModal already needed), built on the shared `Modal` shell, edits * commit immediately via `updatePageSeo` (no separate Save step) rather than * buffering to a "Save" button -- consistent with HeadCodeModal's live-edit * pattern for `design.headCode`. */ export const PageSettingsModal: React.FC = ({ open, onClose, page, updatePageSeo }) => { const [seo, setSeo] = useState(EMPTY_SEO); // Re-sync local form state from the target page's stored seo whenever the // modal opens (or the target page changes while open) -- mirrors the // pattern of resetting form-local state on prop identity change rather // than deriving directly from props (dropdowns/checkboxes need editable // local state). useEffect(() => { if (!open) return; setSeo({ ...EMPTY_SEO, ...(page?.seo || {}) }); }, [open, page?.id, page?.seo]); if (!page) return null; const update = (patch: Partial) => { setSeo((prev) => ({ ...prev, ...patch })); updatePageSeo(page.id, patch); }; return createPortal(
e.stopPropagation()}> {/* Header */}
Page Settings — {page.name}
SEO and social-sharing metadata for this page only.
{/* Body */}
update({ metaTitle: e.target.value })} placeholder={page.name} data-testid="page-seo-meta-title" />

Overrides the browser tab title. Leave blank to use the page name.