From aa77557bf783a279cbd3c9729bc606cb2c7077bc Mon Sep 17 00:00:00 2001 From: "Builder.io" Date: Sat, 15 Nov 2025 20:05:04 +0000 Subject: [PATCH] Create blog editor form component cgen-8f9ee94d8ed24210aae262947896f121 --- client/components/admin/AdminBlogEditor.tsx | 283 ++++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 client/components/admin/AdminBlogEditor.tsx diff --git a/client/components/admin/AdminBlogEditor.tsx b/client/components/admin/AdminBlogEditor.tsx new file mode 100644 index 00000000..3bda7195 --- /dev/null +++ b/client/components/admin/AdminBlogEditor.tsx @@ -0,0 +1,283 @@ +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { useAethexToast } from "@/hooks/use-aethex-toast"; +import { Loader2, X } from "lucide-react"; + +interface BlogEditorProps { + onPublish?: (success: boolean) => void; + initialData?: { + title: string; + excerpt: string; + html: string; + slug?: string; + feature_image?: string; + tags?: string[]; + meta_title?: string; + meta_description?: string; + }; +} + +const BlogEditor = ({ onPublish, initialData }: BlogEditorProps) => { + const toast = useAethexToast(); + const [isLoading, setIsLoading] = useState(false); + const [title, setTitle] = useState(initialData?.title || ""); + const [excerpt, setExcerpt] = useState(initialData?.excerpt || ""); + const [html, setHtml] = useState(initialData?.html || ""); + const [slug, setSlug] = useState(initialData?.slug || ""); + const [featureImage, setFeatureImage] = useState(initialData?.feature_image || ""); + const [tags, setTags] = useState(initialData?.tags || []); + const [tagInput, setTagInput] = useState(""); + const [metaTitle, setMetaTitle] = useState(initialData?.meta_title || ""); + const [metaDescription, setMetaDescription] = useState( + initialData?.meta_description || "", + ); + + // Auto-generate slug from title if not manually set + const autoSlug = + slug || + title + .toLowerCase() + .replace(/[^\w\s-]/g, "") + .trim() + .replace(/\s+/g, "-") + .replace(/-+/g, "-"); + + const addTag = () => { + if (tagInput.trim() && !tags.includes(tagInput.trim())) { + setTags([...tags, tagInput.trim()]); + setTagInput(""); + } + }; + + const removeTag = (tag: string) => { + setTags(tags.filter((t) => t !== tag)); + }; + + const handlePublish = async () => { + if (!title.trim() || !html.trim()) { + toast.error("Title and body are required"); + return; + } + + setIsLoading(true); + try { + const response = await fetch("/api/blog/publish", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + title, + excerpt: excerpt || undefined, + html, + slug: autoSlug, + feature_image: featureImage || undefined, + tags, + meta_title: metaTitle || title, + meta_description: metaDescription || excerpt, + status: "published", + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message || "Failed to publish post"); + } + + const data = await response.json(); + toast.success(`Post published: ${data.url}`); + onPublish?.(true); + + // Reset form + setTitle(""); + setExcerpt(""); + setHtml(""); + setSlug(""); + setFeatureImage(""); + setTags([]); + setMetaTitle(""); + setMetaDescription(""); + } catch (error: any) { + toast.error(error.message || "Failed to publish post"); + onPublish?.(false); + } finally { + setIsLoading(false); + } + }; + + return ( +
+ + + Post Details + + Publish directly to Ghost.org with AeThex as author + + + + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="Post title" + className="border-border/50" + /> +
+ + {/* Slug */} +
+ + setSlug(e.target.value)} + placeholder="Leave blank to auto-generate from title" + className="border-border/50" + /> + {!slug && title && ( +

+ Auto-slug: {autoSlug} +

+ )} +
+ + {/* Excerpt */} +
+ +