Files
igny8/frontend/src/pages/Sites/Editor.tsx
IGNY8 VPS (Salman) 8508af37c7 Refactor Dropdown Handlers and Update WordPress Integration
- Updated dropdown onChange handlers across multiple components to use direct value passing instead of event target value for improved clarity and consistency.
- Changed `url` to `site_url` in WordPress integration configuration for better semantic clarity.
- Enhanced Vite configuration to include `fast-deep-equal` for compatibility with `react-dnd`.
- Updated navigation paths in `SiteContentEditor` and `SiteList` for consistency with new routing structure.
2025-11-18 08:38:59 +00:00

211 lines
7.2 KiB
TypeScript

/**
* Site Content Editor
* Phase 6: Site Integration & Multi-Destination Publishing
* Core CMS features: View all pages/posts, edit page content
*/
import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { EditIcon, EyeIcon, FileTextIcon } from 'lucide-react';
import PageMeta from '../../components/common/PageMeta';
import { Card } from '../../components/ui/card';
import Button from '../../components/ui/button/Button';
import { useToast } from '../../components/ui/toast/ToastContainer';
import { fetchAPI } from '../../services/api';
interface PageBlueprint {
id: number;
slug: string;
title: string;
type: string;
status: string;
order: number;
blocks_json: any[];
site_blueprint: number;
}
interface SiteBlueprint {
id: number;
name: string;
status: string;
}
export default function SiteContentEditor() {
const { id: siteId } = useParams<{ id: string }>();
const navigate = useNavigate();
const toast = useToast();
const [blueprints, setBlueprints] = useState<SiteBlueprint[]>([]);
const [selectedBlueprint, setSelectedBlueprint] = useState<number | null>(null);
const [pages, setPages] = useState<PageBlueprint[]>([]);
const [loading, setLoading] = useState(true);
const [selectedPage, setSelectedPage] = useState<PageBlueprint | null>(null);
useEffect(() => {
if (siteId) {
loadBlueprints();
}
}, [siteId]);
useEffect(() => {
if (selectedBlueprint) {
loadPages(selectedBlueprint);
}
}, [selectedBlueprint]);
const loadBlueprints = async () => {
try {
setLoading(true);
const data = await fetchAPI(`/v1/site-builder/blueprints/?site=${siteId}`);
const blueprintsList = Array.isArray(data?.results) ? data.results : Array.isArray(data) ? data : [];
setBlueprints(blueprintsList);
if (blueprintsList.length > 0) {
setSelectedBlueprint(blueprintsList[0].id);
}
} catch (error: any) {
toast.error(`Failed to load blueprints: ${error.message}`);
} finally {
setLoading(false);
}
};
const loadPages = async (blueprintId: number) => {
try {
const data = await fetchAPI(`/v1/site-builder/pages/?site_blueprint=${blueprintId}`);
const pagesList = Array.isArray(data?.results) ? data.results : Array.isArray(data) ? data : [];
setPages(pagesList.sort((a, b) => a.order - b.order));
} catch (error: any) {
toast.error(`Failed to load pages: ${error.message}`);
}
};
const handleEditPage = (page: PageBlueprint) => {
navigate(`/sites/${siteId}/pages/${page.id}/edit`);
};
const handleViewPage = (page: PageBlueprint) => {
navigate(`/sites/${siteId}/pages/${page.id}`);
};
if (loading) {
return (
<div className="p-6">
<PageMeta title="Site Content Editor" />
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading pages...</div>
</div>
</div>
);
}
return (
<div className="p-6">
<PageMeta title="Site Content Editor - IGNY8" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Site Content Editor
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
View and edit content for site pages
</p>
</div>
{blueprints.length === 0 ? (
<Card className="p-12 text-center">
<p className="text-gray-600 dark:text-gray-400 mb-4">
No site blueprints found for this site
</p>
<Button onClick={() => navigate('/sites/builder')} variant="primary">
Create Site Blueprint
</Button>
</Card>
) : (
<div className="space-y-6">
{blueprints.length > 1 && (
<Card className="p-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Select Blueprint
</label>
<select
value={selectedBlueprint || ''}
onChange={(e) => setSelectedBlueprint(Number(e.target.value))}
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-md dark:bg-gray-800 dark:text-white"
>
{blueprints.map((bp) => (
<option key={bp.id} value={bp.id}>
{bp.name} ({bp.status})
</option>
))}
</select>
</Card>
)}
{pages.length === 0 ? (
<Card className="p-12 text-center">
<p className="text-gray-600 dark:text-gray-400 mb-4">
No pages found in this blueprint
</p>
<Button onClick={() => navigate('/sites/builder')} variant="primary">
Generate Pages
</Button>
</Card>
) : (
<Card className="p-6">
<div className="mb-4">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
Pages ({pages.length})
</h2>
</div>
<div className="space-y-3">
{pages.map((page) => (
<div
key={page.id}
className="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-700 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
>
<div className="flex items-center gap-4 flex-1">
<FileTextIcon className="w-5 h-5 text-gray-400" />
<div className="flex-1">
<h3 className="font-semibold text-gray-900 dark:text-white">
{page.title}
</h3>
<p className="text-sm text-gray-600 dark:text-gray-400">
/{page.slug} {page.type} {page.status}
</p>
{page.blocks_json && page.blocks_json.length > 0 && (
<p className="text-xs text-gray-500 dark:text-gray-500 mt-1">
{page.blocks_json.length} block{page.blocks_json.length !== 1 ? 's' : ''}
</p>
)}
</div>
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => handleViewPage(page)}
title="View"
>
<EyeIcon className="w-4 h-4 mr-1" />
View
</Button>
<Button
variant="primary"
size="sm"
onClick={() => handleEditPage(page)}
title="Edit"
>
<EditIcon className="w-4 h-4 mr-1" />
Edit
</Button>
</div>
</div>
))}
</div>
</Card>
)}
</div>
)}
</div>
);
}