8 Phases refactor
This commit is contained in:
@@ -1,411 +1,43 @@
|
||||
/**
|
||||
* Deployment Panel
|
||||
* Stage 4: Deployment readiness and publishing
|
||||
* Deployment Panel - DEPRECATED
|
||||
*
|
||||
* Displays readiness checklist and deploy/rollback controls
|
||||
* Legacy SiteBlueprint deployment functionality has been removed.
|
||||
* Use WordPress integration sync and publishing features instead.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
import { useToast } from '../../components/ui/toast/ToastContainer';
|
||||
import {
|
||||
CheckCircleIcon,
|
||||
ErrorIcon,
|
||||
AlertIcon,
|
||||
BoltIcon,
|
||||
ArrowRightIcon,
|
||||
FileIcon,
|
||||
BoxIcon,
|
||||
CheckLineIcon,
|
||||
GridIcon
|
||||
} from '../../icons';
|
||||
import {
|
||||
fetchDeploymentReadiness,
|
||||
// fetchSiteBlueprints,
|
||||
DeploymentReadiness,
|
||||
} from '../../services/api';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import { AlertIcon } from '../../icons';
|
||||
|
||||
export default function DeploymentPanel() {
|
||||
const { id: siteId } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const toast = useToast();
|
||||
const [readiness, setReadiness] = useState<DeploymentReadiness | null>(null);
|
||||
const [blueprints, setBlueprints] = useState<any[]>([]);
|
||||
const [selectedBlueprintId, setSelectedBlueprintId] = useState<number | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [deploying, setDeploying] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (siteId) {
|
||||
loadData();
|
||||
}
|
||||
}, [siteId]);
|
||||
|
||||
const loadData = async () => {
|
||||
if (!siteId) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
// const blueprintsData = await fetchSiteBlueprints({ site_id: Number(siteId) });
|
||||
const blueprintsData = null;
|
||||
if (blueprintsData?.results && blueprintsData.results.length > 0) {
|
||||
setBlueprints(blueprintsData.results);
|
||||
const firstBlueprint = blueprintsData.results[0];
|
||||
setSelectedBlueprintId(firstBlueprint.id);
|
||||
await loadReadiness(firstBlueprint.id);
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load deployment data: ${error.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadReadiness = async (blueprintId: number) => {
|
||||
try {
|
||||
const readinessData = await fetchDeploymentReadiness(blueprintId);
|
||||
setReadiness(readinessData);
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to load readiness: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedBlueprintId) {
|
||||
loadReadiness(selectedBlueprintId);
|
||||
}
|
||||
}, [selectedBlueprintId]);
|
||||
|
||||
const handleDeploy = async () => {
|
||||
if (!selectedBlueprintId) return;
|
||||
try {
|
||||
setDeploying(true);
|
||||
const result = await fetchAPI(`/v1/publisher/deploy/${selectedBlueprintId}/`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ check_readiness: true }),
|
||||
});
|
||||
toast.success('Deployment initiated successfully');
|
||||
await loadReadiness(selectedBlueprintId); // Refresh readiness
|
||||
} catch (error: any) {
|
||||
toast.error(`Deployment failed: ${error.message}`);
|
||||
} finally {
|
||||
setDeploying(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRollback = async () => {
|
||||
if (!selectedBlueprintId) return;
|
||||
try {
|
||||
// TODO: Implement rollback endpoint
|
||||
toast.info('Rollback functionality coming soon');
|
||||
} catch (error: any) {
|
||||
toast.error(`Rollback failed: ${error.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getCheckIcon = (passed: boolean) => {
|
||||
return passed ? (
|
||||
<CheckCircleIcon className="w-5 h-5 text-green-600 dark:text-green-400" />
|
||||
) : (
|
||||
<ErrorIcon className="w-5 h-5 text-red-600 dark:text-red-400" />
|
||||
);
|
||||
};
|
||||
|
||||
const getCheckBadge = (passed: boolean) => {
|
||||
return (
|
||||
<Badge color={passed ? 'success' : 'error'} size="sm">
|
||||
{passed ? 'Pass' : 'Fail'}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Deployment Panel" />
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Loading deployment data...</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (blueprints.length === 0) {
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Deployment Panel" />
|
||||
<Card className="p-6">
|
||||
<div className="text-center py-8">
|
||||
<AlertIcon className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-2">No blueprints found for this site</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => navigate(`/sites/${siteId}/builder`)}
|
||||
>
|
||||
Create Blueprint
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const selectedBlueprint = blueprints.find((b) => b.id === selectedBlueprintId);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
<PageMeta title="Deployment Panel" />
|
||||
|
||||
<PageHeader
|
||||
<div className="space-y-6">
|
||||
<PageMeta
|
||||
title="Deployment Panel"
|
||||
badge={{ icon: <BoltIcon />, color: 'orange' }}
|
||||
hideSiteSector
|
||||
description="Legacy deployment features"
|
||||
/>
|
||||
<div className="mb-6 flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => navigate(`/sites/${siteId}`)}
|
||||
>
|
||||
Back to Dashboard
|
||||
<PageHeader
|
||||
title="Deployment Panel"
|
||||
subtitle="This feature has been deprecated"
|
||||
backLink="../"
|
||||
/>
|
||||
|
||||
<Card className="p-8 text-center">
|
||||
<AlertIcon className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
||||
<h2 className="text-2xl font-bold mb-2">Feature Deprecated</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||
The SiteBlueprint deployment system has been removed.
|
||||
Please use WordPress integration sync features for publishing content.
|
||||
</p>
|
||||
<Button onClick={() => navigate('../')} variant="primary">
|
||||
Return to Sites
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleRollback}
|
||||
disabled={!selectedBlueprintId}
|
||||
>
|
||||
<ArrowRightIcon className="w-4 h-4 mr-2 rotate-180" />
|
||||
Rollback
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleDeploy}
|
||||
disabled={deploying || !readiness?.ready || !selectedBlueprintId}
|
||||
>
|
||||
<BoltIcon className={`w-4 h-4 mr-2 ${deploying ? 'animate-pulse' : ''}`} />
|
||||
{deploying ? 'Deploying...' : 'Deploy'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Blueprint Selector */}
|
||||
{blueprints.length > 1 && (
|
||||
<Card className="p-4 mb-6">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Select Blueprint
|
||||
</label>
|
||||
<select
|
||||
value={selectedBlueprintId || ''}
|
||||
onChange={(e) => setSelectedBlueprintId(Number(e.target.value))}
|
||||
className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-white"
|
||||
>
|
||||
{blueprints.map((bp) => (
|
||||
<option key={bp.id} value={bp.id}>
|
||||
{bp.name} ({bp.status})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{selectedBlueprint && (
|
||||
<Card className="p-4 mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
{selectedBlueprint.name}
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
{selectedBlueprint.description || 'No description'}
|
||||
</p>
|
||||
</div>
|
||||
<Badge color={selectedBlueprint.status === 'active' ? 'success' : 'info'} size="md">
|
||||
{selectedBlueprint.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Overall Readiness Status */}
|
||||
{readiness && (
|
||||
<>
|
||||
<Card className="p-6 mb-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Deployment Readiness
|
||||
</h2>
|
||||
<div className="flex items-center gap-2">
|
||||
{getCheckIcon(readiness.ready)}
|
||||
<Badge color={readiness.ready ? 'success' : 'error'} size="md">
|
||||
{readiness.ready ? 'Ready' : 'Not Ready'}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Errors */}
|
||||
{readiness.errors.length > 0 && (
|
||||
<div className="mb-4 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg">
|
||||
<h3 className="text-sm font-semibold text-red-800 dark:text-red-300 mb-2">
|
||||
Blocking Issues
|
||||
</h3>
|
||||
<ul className="space-y-1">
|
||||
{readiness.errors.map((error, idx) => (
|
||||
<li key={idx} className="text-sm text-red-700 dark:text-red-400">
|
||||
• {error}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Warnings */}
|
||||
{readiness.warnings.length > 0 && (
|
||||
<div className="mb-4 p-4 bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg">
|
||||
<h3 className="text-sm font-semibold text-yellow-800 dark:text-yellow-300 mb-2">
|
||||
Warnings
|
||||
</h3>
|
||||
<ul className="space-y-1">
|
||||
{readiness.warnings.map((warning, idx) => (
|
||||
<li key={idx} className="text-sm text-yellow-700 dark:text-yellow-400">
|
||||
• {warning}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Readiness Checks */}
|
||||
<div className="space-y-4">
|
||||
{/* Cluster Coverage */}
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<GridIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">
|
||||
Cluster Coverage
|
||||
</h3>
|
||||
</div>
|
||||
{getCheckBadge(readiness.checks.cluster_coverage)}
|
||||
</div>
|
||||
{readiness.details.cluster_coverage && (
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
<p>
|
||||
{readiness.details.cluster_coverage.covered_clusters} /{' '}
|
||||
{readiness.details.cluster_coverage.total_clusters} clusters covered
|
||||
</p>
|
||||
{readiness.details.cluster_coverage.incomplete_clusters.length > 0 && (
|
||||
<p className="mt-1 text-yellow-600 dark:text-yellow-400">
|
||||
{readiness.details.cluster_coverage.incomplete_clusters.length} incomplete
|
||||
cluster(s)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Content Validation */}
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<CheckLineIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">
|
||||
Content Validation
|
||||
</h3>
|
||||
</div>
|
||||
{getCheckBadge(readiness.checks.content_validation)}
|
||||
</div>
|
||||
{readiness.details.content_validation && (
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
<p>
|
||||
{readiness.details.content_validation.valid_content} /{' '}
|
||||
{readiness.details.content_validation.total_content} content items valid
|
||||
</p>
|
||||
{readiness.details.content_validation.invalid_content.length > 0 && (
|
||||
<p className="mt-1 text-red-600 dark:text-red-400">
|
||||
{readiness.details.content_validation.invalid_content.length} invalid
|
||||
content item(s)
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Taxonomy Completeness */}
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<BoxIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">
|
||||
Taxonomy Completeness
|
||||
</h3>
|
||||
</div>
|
||||
{getCheckBadge(readiness.checks.taxonomy_completeness)}
|
||||
</div>
|
||||
{readiness.details.taxonomy_completeness && (
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
<p>
|
||||
{readiness.details.taxonomy_completeness.total_taxonomies} taxonomies
|
||||
defined
|
||||
</p>
|
||||
{readiness.details.taxonomy_completeness.missing_taxonomies.length > 0 && (
|
||||
<p className="mt-1 text-yellow-600 dark:text-yellow-400">
|
||||
Missing: {readiness.details.taxonomy_completeness.missing_taxonomies.join(', ')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sync Status */}
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<BoltIcon className="w-5 h-5 text-gray-600 dark:text-gray-400" />
|
||||
<h3 className="font-medium text-gray-900 dark:text-white">Sync Status</h3>
|
||||
</div>
|
||||
{getCheckBadge(readiness.checks.sync_status)}
|
||||
</div>
|
||||
{readiness.details.sync_status && (
|
||||
<div className="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
<p>
|
||||
{readiness.details.sync_status.has_integration
|
||||
? 'Integration configured'
|
||||
: 'No integration configured'}
|
||||
</p>
|
||||
{readiness.details.sync_status.mismatch_count > 0 && (
|
||||
<p className="mt-1 text-yellow-600 dark:text-yellow-400">
|
||||
{readiness.details.sync_status.mismatch_count} sync mismatch(es) detected
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => loadReadiness(selectedBlueprintId!)}
|
||||
startIcon={<BoltIcon className="w-4 h-4" />}
|
||||
>
|
||||
Refresh Checks
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleDeploy}
|
||||
disabled={deploying || !readiness.ready}
|
||||
>
|
||||
<BoltIcon className={`w-4 h-4 mr-2 ${deploying ? 'animate-pulse' : ''}`} />
|
||||
{deploying ? 'Deploying...' : 'Deploy Now'}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,210 +1,43 @@
|
||||
/**
|
||||
* Site Content Editor
|
||||
* Phase 6: Site Integration & Multi-Destination Publishing
|
||||
* Core CMS features: View all pages/posts, edit page content
|
||||
* Site Editor - DEPRECATED
|
||||
*
|
||||
* Legacy SiteBlueprint page editor has been removed.
|
||||
* Use Writer module for content creation and editing.
|
||||
*/
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { EditIcon, EyeIcon, FileTextIcon } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PageMeta from '../../components/common/PageMeta';
|
||||
import PageHeader from '../../components/common/PageHeader';
|
||||
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';
|
||||
import { AlertIcon } from '../../icons';
|
||||
|
||||
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 }>();
|
||||
export default function Editor() {
|
||||
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="space-y-6">
|
||||
<PageMeta
|
||||
title="Site Editor"
|
||||
description="Legacy site editor features"
|
||||
/>
|
||||
<PageHeader
|
||||
title="Site Editor"
|
||||
subtitle="This feature has been deprecated"
|
||||
backLink="../"
|
||||
/>
|
||||
|
||||
<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
|
||||
<Card className="p-8 text-center">
|
||||
<AlertIcon className="w-16 h-16 text-amber-500 mx-auto mb-4" />
|
||||
<h2 className="text-2xl font-bold mb-2">Feature Deprecated</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||
The SiteBlueprint page editor has been removed.
|
||||
Please use the Writer module to create and edit content.
|
||||
</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>
|
||||
)}
|
||||
<Button onClick={() => navigate('../')} variant="primary">
|
||||
Return to Sites
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user