/** * Post Editor (Advanced) * Phase 7: Advanced Site Management * Full-featured editing: SEO, metadata, tags, categories, HTML content */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { SaveIcon, XIcon, FileTextIcon, TagIcon, CheckCircleIcon, XCircleIcon, AlertCircleIcon } from 'lucide-react'; import PageMeta from '../../components/common/PageMeta'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Label from '../../components/form/Label'; import TextArea from '../../components/form/input/TextArea'; import SelectDropdown from '../../components/form/SelectDropdown'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { fetchAPI, fetchContentValidation, validateContent, ContentValidationResult } from '../../services/api'; interface Content { id?: number; title: string; content_html: string; content_type: string; // post, page, product, service, category, tag content_structure?: string; // article, listicle, guide, comparison, product_page status: string; // draft, published site?: number; cluster_id?: number | null; cluster_name?: string | null; taxonomy_terms?: Array<{ id: number; name: string; taxonomy: string }> | null; source?: string; // igny8, wordpress external_id?: string | null; external_url?: string | null; word_count?: number; created_at?: string; updated_at?: string; } export default function PostEditor() { const { id: siteId, postId } = useParams<{ id: string; postId?: string }>(); const navigate = useNavigate(); const toast = useToast(); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [activeTab, setActiveTab] = useState<'content' | 'taxonomy' | 'validation'>('content'); const [validationResult, setValidationResult] = useState(null); const [validating, setValidating] = useState(false); const [content, setContent] = useState({ title: '', content_html: '', content_type: 'post', content_structure: 'article', status: 'draft', site: Number(siteId), source: 'igny8', taxonomy_terms: [], }); useEffect(() => { if (siteId) { loadSite(); if (postId && postId !== 'new') { loadPost(); loadValidation(); } else { setLoading(false); } } }, [siteId, postId]); const loadValidation = async () => { if (!postId || postId === 'new') return; try { const result = await fetchContentValidation(Number(postId)); setValidationResult(result); } catch (error: any) { console.error('Failed to load validation:', error); toast.error(`Failed to load validation: ${error.message || 'Unknown error'}`); } }; const handleValidate = async () => { if (!content.id) { toast.error('Please save the content first before validating'); return; } try { setValidating(true); const result = await validateContent(content.id); await loadValidation(); if (result.is_valid) { toast.success('Content validation passed!'); } else { toast.warning(`Validation found ${result.errors.length} issue(s)`); } } catch (error: any) { toast.error(`Validation failed: ${error.message}`); } finally { setValidating(false); } }; const loadSite = async () => { try { const site = await fetchAPI(`/v1/auth/sites/${siteId}/`); if (site) { setContent((prev) => ({ ...prev, sector: site.sector || 0, })); } } catch (error: any) { console.error('Failed to load site:', error); } }; const loadPost = async () => { try { setLoading(true); const data = await fetchAPI(`/v1/writer/content/${postId}/`); if (data) { setContent({ id: data.id, title: data.title || '', content_html: data.content_html || '', content_type: data.content_type || 'post', content_structure: data.content_structure || 'article', status: data.status || 'draft', site: data.site || Number(siteId), cluster_id: data.cluster_id || null, cluster_name: data.cluster_name || null, taxonomy_terms: Array.isArray(data.taxonomy_terms) ? data.taxonomy_terms : [], source: data.source || 'igny8', external_id: data.external_id || null, external_url: data.external_url || null, word_count: data.word_count || 0, created_at: data.created_at, updated_at: data.updated_at, }); } } catch (error: any) { toast.error(`Failed to load post: ${error.message}`); navigate(`/sites/${siteId}/content`); } finally { setLoading(false); } }; const handleSave = async () => { if (!content.title.trim()) { toast.error('Title is required'); return; } try { setSaving(true); const payload = { title: content.title, content_html: content.content_html || content.content, content_type: content.content_type, content_structure: content.content_structure, status: content.status, site: content.site, cluster_id: content.cluster_id, taxonomy_terms: content.taxonomy_terms, source: content.source || 'igny8', external_id: content.external_id, external_url: content.external_url, }; if (content.id) { // Update existing await fetchAPI(`/v1/writer/content/${content.id}/`, { method: 'PUT', body: JSON.stringify(payload), }); toast.success('Post updated successfully'); } else { // Create new const result = await fetchAPI('/v1/writer/content/', { method: 'POST', body: JSON.stringify({ ...payload, }), }); toast.success('Post created successfully'); if (result?.id) { navigate(`/sites/${siteId}/posts/${result.id}/edit`); } } } catch (error: any) { toast.error(`Failed to save post: ${error.message}`); } finally { setSaving(false); } }; const CONTENT_TYPES = [ { value: 'post', label: 'Post' }, { value: 'page', label: 'Page' }, { value: 'product', label: 'Product' }, { value: 'service', label: 'Service' }, { value: 'category', label: 'Category' }, { value: 'tag', label: 'Tag' }, ]; const CONTENT_STRUCTURES = [ { value: 'article', label: 'Article' }, { value: 'listicle', label: 'Listicle' }, { value: 'guide', label: 'Guide' }, { value: 'comparison', label: 'Comparison' }, { value: 'product_page', label: 'Product Page' }, ]; const STATUS_OPTIONS = [ { value: 'draft', label: 'Draft' }, { value: 'published', label: 'Published' }, ]; if (loading) { return (
Loading post...
); } return (
{/* Main Content Area */}

{content.id ? 'Edit Post' : 'New Post'}

{content.id ? 'Edit your post content' : 'Create a new post'}

{/* Tabs */}
{content.id && ( )}
{/* Content Tab */} {activeTab === 'content' && (
setContent({ ...content, title: e.target.value })} placeholder="Enter post title" className="mt-1 w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white" />
setContent({ ...content, content_type: value })} />
setContent({ ...content, status: value })} />