"use client"; import "quill/dist/quill.snow.css"; import { useEffect, useRef, useState } from "react"; import type Quill from "quill"; import type { BenefitsContent, ContentBlock, CtaContent, HeroContent, PageSlug, RichtextContent, TestimonialContent } from "../../lib/cms"; import { DEFAULT_LANG, LANG_LABELS, type Lang } from "../../lib/i18n"; import { RICHTEXT_VARIANTS, RICHTEXT_VARIANT_LABELS, type RichtextVariant } from "../../lib/richtextVariants"; import { updateBlockAction } from "./actions"; import { ImageField } from "./ImageField"; import { Modal } from "./Modal"; import { errorClass, hintClass, inputClass, labelClass, primaryButton, secondaryButton, textareaClass } from "./ui"; function useRichText(initialHtml: string) { const editorRef = useRef(null); const quillRef = useRef(null); useEffect(() => { let cancelled = false; import("quill").then(({ default: Quill }) => { if (cancelled || !editorRef.current || quillRef.current) return; const quill = new Quill(editorRef.current, { theme: "snow", modules: { toolbar: [["bold", "italic", "underline"], [{ list: "ordered" }, { list: "bullet" }], ["link"], ["clean"]], }, }); if (initialHtml) quill.clipboard.dangerouslyPasteHTML(initialHtml); quillRef.current = quill; }); return () => { cancelled = true; quillRef.current = null; }; }, []); const getHtml = () => quillRef.current?.root.innerHTML ?? initialHtml; const setHtml = (html: string) => { if (quillRef.current) quillRef.current.setContents(quillRef.current.clipboard.convert({ html })); }; return { editorRef, getHtml, setHtml }; } function RichTextField({ label, editorRef }: { label: string; editorRef: React.RefObject }) { return (
{label}
); } function TextInput({ label, value, onChange, required }: { label: string; value: string; onChange: (v: string) => void; required?: boolean }) { return ( ); } type AnyContent = HeroContent & RichtextContent & TestimonialContent & BenefitsContent & CtaContent; export function BlockEditModal({ block, slug, lang, translation, mediaImages, onClose, onSaved, }: { block: ContentBlock; slug: PageSlug; lang: Lang; // EN/DE: de bestaande vertaling van dit blok (of undefined) translation?: Record; mediaImages: string[]; onClose: () => void; onSaved: () => void; }) { const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const isNl = lang === DEFAULT_LANG; const nlContent = JSON.parse(block.content) as AnyContent; // EN/DE opent leeg (niet voorgevuld met NL), anders telt een ongewijzigde kopie als "vertaald" const initial = (isNl ? nlContent : (translation ?? {})) as AnyContent; const [image, setImage] = useState(initial.image ?? ""); const [buttonLabel, setButtonLabel] = useState(initial.buttonLabel ?? ""); const [buttonHref, setButtonHref] = useState(initial.buttonHref ?? ""); const [attribution, setAttribution] = useState(initial.attribution ?? ""); const [plainHeading, setPlainHeading] = useState(initial.heading ?? ""); const [itemsText, setItemsText] = useState((initial.items ?? []).join("\n")); const [variant, setVariant] = useState(nlContent.variant ?? "standaard"); const variantEditable = isNl && block.type === "richtext"; const heading = useRichText(block.type === "hero" || block.type === "richtext" ? (initial.heading ?? "") : ""); const text = useRichText(block.type === "hero" ? (initial.text ?? "") : ""); const body = useRichText(block.type === "richtext" || block.type === "cta" ? (initial.body ?? "") : ""); const quote = useRichText(block.type === "testimonial" ? (initial.quote ?? "") : ""); const imageEmptyLabel = isNl ? "Geen afbeelding" : "Leeg = NL-afbeelding gebruiken"; // Nederlandse tekstvelden als startpunt om te vertalen (afbeeldingen/links blijven leeg = NL) function copyFromNl() { if (nlContent.buttonLabel !== undefined) setButtonLabel(nlContent.buttonLabel ?? ""); if (nlContent.attribution !== undefined) setAttribution(nlContent.attribution ?? ""); if (block.type === "benefits") { setPlainHeading(nlContent.heading ?? ""); setItemsText((nlContent.items ?? []).join("\n")); } if (block.type === "cta") { setPlainHeading(nlContent.heading ?? ""); body.setHtml(nlContent.body ?? ""); } if (block.type === "hero") { heading.setHtml(nlContent.heading ?? ""); text.setHtml(nlContent.text ?? ""); } if (block.type === "richtext") { heading.setHtml(nlContent.heading ?? ""); body.setHtml(nlContent.body ?? ""); } if (block.type === "testimonial") quote.setHtml(nlContent.quote ?? ""); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setSaving(true); setError(null); try { let content: Record; switch (block.type) { case "hero": content = { heading: heading.getHtml(), text: text.getHtml(), image, buttonLabel, buttonHref }; break; case "richtext": content = { heading: heading.getHtml(), body: body.getHtml(), buttonLabel, buttonHref }; if (variantEditable) content.variant = variant; break; case "testimonial": content = { quote: quote.getHtml(), attribution, image }; break; case "benefits": content = { heading: plainHeading, items: itemsText.split("\n").map((s) => s.trim()).filter(Boolean) }; break; case "cta": content = { heading: plainHeading, body: body.getHtml(), buttonLabel, buttonHref }; break; } const result = await updateBlockAction(block.id, slug, content, lang); if (result.error) setError(result.error); else { onSaved(); onClose(); } } finally { setSaving(false); } } const buttonFields = (required: boolean) => ( <> ); return (
{!isNl && (

Je vertaalt naar {LANG_LABELS[lang]}. Leeg gelaten velden vallen terug op het Nederlands.

Nederlandse tekst (referentie)
{[nlContent.heading, nlContent.text, nlContent.body, nlContent.quote, nlContent.attribution] .concat(nlContent.items ?? []) .filter((v): v is string => typeof v === "string" && v.trim() !== "") .map((v, i) => (

{v.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim()}

))}
)} {block.type === "hero" && ( <> {buttonFields(false)} )} {block.type === "richtext" && ( <> {variantEditable && ( )} {buttonFields(false)} )} {block.type === "testimonial" && ( <> )} {block.type === "benefits" && ( <>