"use client" import Link from "next/link" import { useEffect, useEffectEvent, useState, useTransition } from "react" import { ArrowLeftIcon, ExternalLinkIcon } from "lucide-react" import { Button, buttonVariants } from "@/components/ui/button" import { cn } from "@/lib/utils" import { saveVariant } from "../../actions/variants" import type { ShareState } from "../../lib/preview" import { getTemplate } from "../../lib/registry" import type { ListItem, TemplateContent } from "../../lib/types" import { AiPanel } from "../components/editor/ai-panel" import { BlocksPanel } from "../components/editor/blocks-panel" import { ContentPanel } from "../components/editor/content-panel" import { DesignPanel } from "../components/editor/design-panel" import { PreviewFrame } from "../components/editor/preview-frame" import { SharePanel } from "../components/editor/share-panel" export type EditorVariant = { id: string name: string templateKey: string clientId: string | null content: TemplateContent shareToken: string | null shareExpiresAt: string | null shareState: ShareState } const tabs = [ { key: "content", label: "Inhalte" }, { key: "design", label: "Design" }, { key: "ai", label: "KI" }, { key: "share", label: "Teilen" }, ] as const type Tab = (typeof tabs)[number]["key"] function snapshot(name: string, clientId: string | null, content: TemplateContent) { return JSON.stringify({ name, clientId, content }) } export function VariantEditor({ variant, clients }: { variant: EditorVariant; clients: { id: string; name: string }[] }) { const def = getTemplate(variant.templateKey) const [name, setName] = useState(variant.name) const [clientId, setClientId] = useState(variant.clientId) const [content, setContent] = useState(variant.content) const [saved, setSaved] = useState(() => snapshot(variant.name, variant.clientId, variant.content)) const [tab, setTab] = useState("content") // Baukasten: Baustein, zu dem die Vorschau springt (n zählt hoch, damit erneutes Aufklappen wieder springt) const [focus, setFocus] = useState<{ id: string; n: number } | null>(null) const [error, setError] = useState(null) const [pending, startTransition] = useTransition() const current = snapshot(name, clientId, content) const dirty = current !== saved function save() { startTransition(async () => { const result = await saveVariant(variant.id, { name, clientId, content }) if ("error" in result) { setError(result.error) return } setError(null) setSaved(current) }) } const onShortcut = useEffectEvent((event: KeyboardEvent) => { if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "s") { event.preventDefault() if (dirty && !pending) save() } }) useEffect(() => { window.addEventListener("keydown", onShortcut) return () => window.removeEventListener("keydown", onShortcut) }, []) useEffect(() => { if (!dirty) return const warn = (event: BeforeUnloadEvent) => event.preventDefault() window.addEventListener("beforeunload", warn) return () => window.removeEventListener("beforeunload", warn) }, [dirty]) if (!def) return null function updateField(section: string, field: string, value: string | ListItem[]) { setContent((c) => ({ ...c, data: { ...c.data, [section]: { ...c.data[section], [field]: value } } })) } return (
{ if (dirty && !window.confirm("Ungespeicherte Änderungen verwerfen?")) event.preventDefault() }} >
setName(event.target.value)} aria-label="Name der Variante" className="w-full truncate rounded-md bg-transparent text-xl font-semibold outline-none focus-visible:ring-2 focus-visible:ring-ring/50 md:text-2xl" />

Vorlage „{def.name}“ · {def.industry}

{pending ? "Speichert …" : dirty ? "Ungespeicherte Änderungen" : "Gespeichert"} Vollbild
{error &&

{error}

}
) }