Enhance site deployment and preview functionality. Updated Preview.tsx to improve deployment record handling and fallback URL logic. Added deploy functionality in Blueprints.tsx and Preview.tsx, including loading states and user feedback. Introduced ProgressModal for page generation status updates. Updated siteBuilder.api.ts to include deployBlueprint API method.
This commit is contained in:
@@ -4,7 +4,7 @@ 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 { FileText, Loader2, Plus, Trash2, CheckSquare, Square } from "lucide-react";
|
||||
import { FileText, Loader2, Plus, Trash2, CheckSquare, Square, Rocket } from "lucide-react";
|
||||
import { useSiteStore } from "../../../store/siteStore";
|
||||
import { useBuilderStore } from "../../../store/builderStore";
|
||||
import { siteBuilderApi } from "../../../services/siteBuilder.api";
|
||||
@@ -25,6 +25,7 @@ export default function SiteBuilderBlueprints() {
|
||||
blueprint: SiteBlueprint | null;
|
||||
isBulk: boolean;
|
||||
}>({ isOpen: false, blueprint: null, isBulk: false });
|
||||
const [deployingId, setDeployingId] = useState<number | null>(null);
|
||||
|
||||
const loadBlueprints = async (siteId: number) => {
|
||||
try {
|
||||
@@ -113,6 +114,27 @@ export default function SiteBuilderBlueprints() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeploy = async (blueprint: SiteBlueprint) => {
|
||||
try {
|
||||
setDeployingId(blueprint.id);
|
||||
const result = await siteBuilderApi.deployBlueprint(blueprint.id);
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Site deployed successfully!");
|
||||
// Reload blueprints to get updated status
|
||||
if (activeSite?.id) {
|
||||
await loadBlueprints(activeSite.id);
|
||||
}
|
||||
} else {
|
||||
toast.error("Failed to deploy site");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message || "Failed to deploy site");
|
||||
} finally {
|
||||
setDeployingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
<PageMeta title="Blueprints - IGNY8" />
|
||||
@@ -238,6 +260,17 @@ export default function SiteBuilderBlueprints() {
|
||||
<span className="capitalize">{blueprint.status}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{(blueprint.status === 'ready' || blueprint.status === 'deployed') && (
|
||||
<Button
|
||||
variant="solid"
|
||||
tone="brand"
|
||||
disabled={deployingId === blueprint.id}
|
||||
onClick={() => handleDeploy(blueprint)}
|
||||
startIcon={deployingId === blueprint.id ? <Loader2 className="h-4 w-4 animate-spin" /> : <Rocket className="h-4 w-4" />}
|
||||
>
|
||||
{deployingId === blueprint.id ? "Deploying..." : blueprint.status === 'deployed' ? "Redeploy" : "Deploy Site"}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="outline"
|
||||
tone="brand"
|
||||
@@ -275,6 +308,7 @@ export default function SiteBuilderBlueprints() {
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PageMeta from "../../../components/common/PageMeta";
|
||||
import {
|
||||
@@ -10,17 +10,69 @@ import Button from "../../../components/ui/button/Button";
|
||||
import Alert from "../../../components/ui/alert/Alert";
|
||||
import { useBuilderStore } from "../../../store/builderStore";
|
||||
import { useSiteDefinitionStore } from "../../../store/siteDefinitionStore";
|
||||
import { Eye } from "lucide-react";
|
||||
import ProgressModal from "../../../components/common/ProgressModal";
|
||||
import { Eye, Play, Loader2, Rocket } from "lucide-react";
|
||||
import { useToast } from "../../../components/ui/toast/ToastContainer";
|
||||
import { siteBuilderApi } from "../../../services/siteBuilder.api";
|
||||
|
||||
export default function SiteBuilderPreview() {
|
||||
const navigate = useNavigate();
|
||||
const { activeBlueprint, pages } = useBuilderStore();
|
||||
const toast = useToast();
|
||||
const {
|
||||
activeBlueprint,
|
||||
pages,
|
||||
generateAllPages,
|
||||
isGenerating,
|
||||
generationProgress
|
||||
} = useBuilderStore();
|
||||
const { structure, selectedSlug, selectPage } = useSiteDefinitionStore();
|
||||
const [showProgress, setShowProgress] = useState(false);
|
||||
const [isDeploying, setIsDeploying] = useState(false);
|
||||
|
||||
const selectedPageDefinition = useMemo(() => {
|
||||
return structure?.pages?.find((page) => page.slug === selectedSlug);
|
||||
}, [structure, selectedSlug]);
|
||||
|
||||
useEffect(() => {
|
||||
if (generationProgress?.celeryTaskId) {
|
||||
setShowProgress(true);
|
||||
}
|
||||
}, [generationProgress?.celeryTaskId]);
|
||||
|
||||
const handleGenerateAll = async () => {
|
||||
if (!activeBlueprint) return;
|
||||
|
||||
setShowProgress(true);
|
||||
try {
|
||||
await generateAllPages(activeBlueprint.id);
|
||||
toast.success("Page generation queued successfully");
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message || "Failed to generate pages");
|
||||
setShowProgress(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeploy = async () => {
|
||||
if (!activeBlueprint) return;
|
||||
|
||||
try {
|
||||
setIsDeploying(true);
|
||||
const result = await siteBuilderApi.deployBlueprint(activeBlueprint.id);
|
||||
|
||||
if (result.success) {
|
||||
toast.success("Site deployed successfully!");
|
||||
// Reload blueprint to get updated status
|
||||
await useBuilderStore.getState().loadBlueprint(activeBlueprint.id);
|
||||
} else {
|
||||
toast.error("Failed to deploy site");
|
||||
}
|
||||
} catch (error: any) {
|
||||
toast.error(error?.message || "Failed to deploy site");
|
||||
} finally {
|
||||
setIsDeploying(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!activeBlueprint) {
|
||||
return (
|
||||
<div className="space-y-6 p-6">
|
||||
@@ -58,9 +110,33 @@ export default function SiteBuilderPreview() {
|
||||
Inspect the generated structure before publishing or deploying.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" onClick={() => navigate("/sites/builder")}>
|
||||
Back to wizard
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
{activeBlueprint.status === 'ready' && (
|
||||
<Button
|
||||
variant="solid"
|
||||
tone="brand"
|
||||
onClick={handleGenerateAll}
|
||||
disabled={isGenerating}
|
||||
startIcon={isGenerating ? <Loader2 className="h-4 w-4 animate-spin" /> : <Play className="h-4 w-4" />}
|
||||
>
|
||||
{isGenerating ? "Generating..." : "Generate All Pages"}
|
||||
</Button>
|
||||
)}
|
||||
{(activeBlueprint.status === 'ready' || activeBlueprint.status === 'deployed') && (
|
||||
<Button
|
||||
variant="solid"
|
||||
tone="brand"
|
||||
onClick={handleDeploy}
|
||||
disabled={isDeploying}
|
||||
startIcon={isDeploying ? <Loader2 className="h-4 w-4 animate-spin" /> : <Rocket className="h-4 w-4" />}
|
||||
>
|
||||
{isDeploying ? "Deploying..." : activeBlueprint.status === 'deployed' ? "Redeploy" : "Deploy Site"}
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="outline" onClick={() => navigate("/sites/builder")}>
|
||||
Back to wizard
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Alert
|
||||
@@ -157,6 +233,29 @@ export default function SiteBuilderPreview() {
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<ProgressModal
|
||||
isOpen={showProgress}
|
||||
onClose={() => setShowProgress(false)}
|
||||
title="Generating Pages"
|
||||
percentage={isGenerating ? 50 : 100}
|
||||
status={isGenerating ? 'processing' : generationProgress ? 'completed' : 'pending'}
|
||||
message={
|
||||
isGenerating
|
||||
? `Generating content for ${generationProgress?.pagesQueued || pages.length} page${(generationProgress?.pagesQueued || pages.length) !== 1 ? 's' : ''}...`
|
||||
: 'Generation completed!'
|
||||
}
|
||||
details={
|
||||
generationProgress
|
||||
? {
|
||||
current: generationProgress.pagesQueued,
|
||||
total: generationProgress.pagesQueued,
|
||||
completed: generationProgress.pagesQueued,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
taskId={generationProgress?.celeryTaskId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -37,30 +37,51 @@ export default function SitePreview() {
|
||||
const latestBlueprint = blueprints[0];
|
||||
setBlueprint(latestBlueprint);
|
||||
|
||||
// Get publishing status to find preview URL
|
||||
if (latestBlueprint.deployed_version) {
|
||||
// Try to get publishing record
|
||||
// Get deployment record to find preview URL
|
||||
if (latestBlueprint.deployed_version || latestBlueprint.status === 'deployed') {
|
||||
try {
|
||||
const publishingData = await fetchAPI(`/v1/publishing/records/?site_blueprint=${latestBlueprint.id}`);
|
||||
const records = Array.isArray(publishingData?.results) ? publishingData.results : Array.isArray(publishingData) ? publishingData : [];
|
||||
// Get deployment records for this blueprint
|
||||
const deploymentsData = await fetchAPI(`/v1/publisher/deployments/?site_blueprint=${latestBlueprint.id}`);
|
||||
const deployments = Array.isArray(deploymentsData?.results) ? deploymentsData.results : Array.isArray(deploymentsData) ? deploymentsData : [];
|
||||
|
||||
if (records.length > 0) {
|
||||
const record = records.find((r: any) => r.status === 'published') || records[0];
|
||||
if (record?.published_url) {
|
||||
setPreviewUrl(record.published_url);
|
||||
}
|
||||
// Find the latest deployed record
|
||||
const deployedRecord = deployments.find((d: any) => d.status === 'deployed') || deployments[0];
|
||||
|
||||
if (deployedRecord?.deployment_url) {
|
||||
setPreviewUrl(deployedRecord.deployment_url);
|
||||
} else if (deployedRecord) {
|
||||
// If deployment exists but no URL, construct from Sites Renderer
|
||||
// Sites Renderer should be accessible at a different port or subdomain
|
||||
// Check if we have the Sites Renderer URL configured
|
||||
const sitesRendererUrl = import.meta.env.VITE_SITES_RENDERER_URL ||
|
||||
(window as any).__SITES_RENDERER_URL__ ||
|
||||
'http://localhost:8024';
|
||||
setPreviewUrl(`${sitesRendererUrl}/${siteId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// If no publishing record, construct preview URL from blueprint
|
||||
console.warn('No publishing record found, using fallback URL');
|
||||
console.warn('No deployment record found:', error);
|
||||
// If blueprint is deployed but no deployment record, try Sites Renderer directly
|
||||
if (latestBlueprint.status === 'deployed') {
|
||||
const sitesRendererUrl = import.meta.env.VITE_SITES_RENDERER_URL ||
|
||||
(window as any).__SITES_RENDERER_URL__ ||
|
||||
'http://localhost:8024';
|
||||
setPreviewUrl(`${sitesRendererUrl}/${siteId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: construct preview URL from blueprint
|
||||
if (!previewUrl && latestBlueprint.id) {
|
||||
// Assuming sites are hosted at a subdomain or path
|
||||
const baseUrl = window.location.origin;
|
||||
setPreviewUrl(`${baseUrl}/sites/${siteId}/preview/${latestBlueprint.id}`);
|
||||
// If still no preview URL, check blueprint status
|
||||
if (!previewUrl) {
|
||||
if (latestBlueprint.status === 'ready' || latestBlueprint.status === 'generating' || latestBlueprint.status === 'draft') {
|
||||
// Blueprint exists but not deployed yet - don't set preview URL
|
||||
setPreviewUrl(null);
|
||||
} else if (latestBlueprint.status === 'deployed') {
|
||||
// Blueprint is deployed but no deployment record found - try Sites Renderer
|
||||
const sitesRendererUrl = import.meta.env.VITE_SITES_RENDERER_URL ||
|
||||
(window as any).__SITES_RENDERER_URL__ ||
|
||||
'http://localhost:8024';
|
||||
setPreviewUrl(`${sitesRendererUrl}/${siteId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
@@ -110,12 +131,31 @@ export default function SitePreview() {
|
||||
</div>
|
||||
<Card className="p-12 text-center">
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||
No preview available. Please deploy your site first.
|
||||
{blueprint?.status === 'ready' || blueprint?.status === 'generating'
|
||||
? 'Blueprint is ready but not yet deployed. Deploy your site to preview it.'
|
||||
: blueprint?.status === 'draft'
|
||||
? 'Blueprint is still in draft. Complete the wizard to generate the site structure.'
|
||||
: 'No preview available. Please deploy your site first.'}
|
||||
</p>
|
||||
{blueprint && (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-500">
|
||||
Blueprint: {blueprint.name} ({blueprint.status})
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-500 dark:text-gray-500">
|
||||
Blueprint: {blueprint.name} ({blueprint.status})
|
||||
</p>
|
||||
{blueprint.status === 'ready' && (
|
||||
<Button
|
||||
variant="solid"
|
||||
tone="brand"
|
||||
onClick={() => {
|
||||
// Navigate to blueprints page or show deploy option
|
||||
window.location.href = `/sites/blueprints`;
|
||||
}}
|
||||
className="mt-4"
|
||||
>
|
||||
Go to Blueprints to Deploy
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -157,5 +157,16 @@ export const siteBuilderApi = {
|
||||
body: JSON.stringify({ ids }),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Deploy a blueprint to Sites renderer
|
||||
*/
|
||||
async deployBlueprint(blueprintId: number): Promise<{ success: boolean; deployment_url?: string; deployment_id?: number }> {
|
||||
const response = await fetchAPI(`/v1/publisher/deploy/${blueprintId}/`, {
|
||||
method: 'POST',
|
||||
});
|
||||
// Handle unified response format
|
||||
return response?.data || response;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user