"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import type { BlockType, ContentBlock, Page, PageSlug, PageTranslation } from "../../lib/cms"; import { DEFAULT_LANG, LANGS, LANG_LABELS, type Lang } from "../../lib/i18n"; import { RICHTEXT_VARIANT_LABELS, isRichtextVariant } from "../../lib/richtextVariants"; import { createContentBlockAction, deleteContentBlockAction, moveContentBlockAction, updatePageNavAction, updatePageSeoAction, } from "./actions"; import { BlockEditModal } from "./BlockEditModal"; import { ImageField } from "./ImageField"; import { Modal } from "./Modal"; import { LangDot } from "./PagesOverview"; import { cardClass, errorClass, hintClass, inputClass, labelClass, primaryButton, savedClass, secondaryButton, textareaClass, } from "./ui"; export const TYPE_LABELS: Record = { hero: "Hero", richtext: "Tekstblok", testimonial: "Testimonial", benefits: "Voordelenlijst", cta: "Call to action", }; const BLOCK_TYPE_OPTIONS: BlockType[] = ["hero", "richtext", "testimonial", "benefits", "cta"]; type BlockTranslations = Record>>; // "Tekstblok · Kaart – grijs": de gekozen weergave zichtbaar zonder het blok te openen function variantSuffix(block: ContentBlock): string { if (block.type !== "richtext") return ""; try { const variant = (JSON.parse(block.content) as { variant?: unknown }).variant; return isRichtextVariant(variant) && variant !== "standaard" ? ` · ${RICHTEXT_VARIANT_LABELS[variant]}` : ""; } catch { return ""; } } export function PageEditor({ slug, page, blocks, mediaImages, pageTranslations, blockTranslations, }: { slug: PageSlug; page: Page; blocks: ContentBlock[]; mediaImages: string[]; pageTranslations: Record; blockTranslations: BlockTranslations; }) { const router = useRouter(); const [lang, setLang] = useState(DEFAULT_LANG); const [editingBlock, setEditingBlock] = useState(null); const [addBlockOpen, setAddBlockOpen] = useState(false); const [busyBlockId, setBusyBlockId] = useState(null); const isNl = lang === DEFAULT_LANG; // Een taal is "ingevuld" zodra er ≥1 vertaald blok is const translatedCount = (l: Lang) => blocks.filter((b) => blockTranslations[l]?.[b.id]).length; async function handleDeleteBlock(block: ContentBlock) { if (!confirm(`Blok "${block.label}" verwijderen, met de vertalingen?`)) return; setBusyBlockId(block.id); try { const result = await deleteContentBlockAction(block.id, slug); if (result.error) alert(result.error); else router.refresh(); } finally { setBusyBlockId(null); } } async function handleMoveBlock(block: ContentBlock, direction: "up" | "down") { setBusyBlockId(block.id); try { await moveContentBlockAction(block.id, slug, direction); router.refresh(); } finally { setBusyBlockId(null); } } return (
{LANGS.map((l) => ( ))}
{!isNl && (

{LANG_LABELS[lang]}: {translatedCount(lang)} van {blocks.length} blokken vertaald. {translatedCount(lang) === 0 ? " De taal telt als ingevuld zodra minstens één blok is vertaald." : " Niet-vertaalde velden vallen terug op het Nederlands."}

)}

Inhoud{isNl ? "" : ` (${lang.toUpperCase()})`}

{isNl && ( )}
{blocks.length === 0 &&

Nog geen inhoud — voeg een blok toe.

} {blocks.map((block, index) => { const translated = Boolean(blockTranslations[lang]?.[block.id]); const busy = busyBlockId === block.id; return (
{TYPE_LABELS[block.type]} {variantSuffix(block)} {!isNl && ( {translated ? "Vertaald" : "Niet vertaald"} )}
{block.label}
{isNl && ( <> )}
); })}
{editingBlock && ( setEditingBlock(null)} onSaved={() => router.refresh()} /> )} {addBlockOpen && setAddBlockOpen(false)} onCreated={() => router.refresh()} />}
); } // Apart component met key={lang}, zodat de beginwaarde bij een taalwissel opnieuw wordt gezet function NavForm({ slug, lang, nlLabel, initialLabel }: { slug: PageSlug; lang: Lang; nlLabel: string; initialLabel: string }) { const router = useRouter(); const [navLabel, setNavLabel] = useState(initialLabel); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); const isNl = lang === DEFAULT_LANG; async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); setSaved(false); try { const result = await updatePageNavAction(slug, navLabel, lang); if (result.error) setError(result.error); else { setSaved(true); router.refresh(); } } finally { setSaving(false); } } return (

Navigatie

{error &&

{error}

} {saved &&

Opgeslagen.

}
); } function SeoForm({ slug, lang, page, translation, mediaImages, }: { slug: PageSlug; lang: Lang; page: Page; translation?: PageTranslation; mediaImages: string[]; }) { const router = useRouter(); const isNl = lang === DEFAULT_LANG; const [metaTitle, setMetaTitle] = useState(isNl ? page.meta_title : (translation?.meta_title ?? "")); const [metaDescription, setMetaDescription] = useState(isNl ? page.meta_description : (translation?.meta_description ?? "")); const [ogImage, setOgImage] = useState(page.og_image ?? ""); const [canonicalUrl, setCanonicalUrl] = useState(page.canonical_url ?? ""); const [noindex, setNoindex] = useState(Boolean(page.noindex)); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [saved, setSaved] = useState(false); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); setSaved(false); try { const result = await updatePageSeoAction(slug, metaTitle, metaDescription, ogImage, canonicalUrl, noindex, lang); if (result.error) setError(result.error); else { setSaved(true); router.refresh(); } } finally { setSaving(false); } } return (

SEO / Google-optimalisatie