This commit is contained in:
IGNY8 VPS (Salman)
2025-11-29 07:20:26 +00:00
parent 341650bddc
commit 4bea79a76d
21 changed files with 443 additions and 1065 deletions

View File

@@ -202,13 +202,6 @@ export default function SiteDashboard() {
</p>
)}
<div className="flex gap-2 mt-4">
<Button
variant="outline"
onClick={() => navigate(`/sites/${siteId}/preview`)}
startIcon={<EyeIcon className="w-4 h-4" />}
>
Preview
</Button>
<Button
variant="primary"
onClick={() => navigate(`/sites/${siteId}/settings`)}

View File

@@ -382,12 +382,16 @@ export default function SiteList() {
{filteredSites.map((site) => (
<Card key={site.id} className="rounded-xl border-2 border-slate-200 bg-white dark:border-gray-800 dark:bg-white/3 hover:border-[var(--color-primary)] hover:shadow-lg transition-all">
<div className="relative p-4 pb-6">
<div className="mb-5 size-12 rounded-xl bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-lg">
<GridIcon className="h-6 w-6" />
<div className="flex items-center gap-3 mb-4">
<div className="size-6 rounded-lg bg-gradient-to-br from-[var(--color-primary)] to-[var(--color-primary-dark)] flex items-center justify-center text-white shadow-md flex-shrink-0">
<svg className="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-800 dark:text-white/90">
{site.name}
</h3>
</div>
<h3 className="mb-3 text-lg font-semibold text-gray-800 dark:text-white/90">
{site.name}
</h3>
<p className="max-w-xs text-sm text-gray-500 dark:text-gray-400 mb-2">
{site.description || 'No description'}
</p>

View File

@@ -1,223 +0,0 @@
/**
* Site Preview
* Phase 7: Advanced Site Management
* Features: Live iframe preview of deployed site
*/
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { RefreshCwIcon, ExternalLinkIcon, Maximize2Icon, Minimize2Icon } 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';
export default function SitePreview() {
const { id: siteId } = useParams<{ id: string }>();
const toast = useToast();
const [loading, setLoading] = useState(true);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isFullscreen, setIsFullscreen] = useState(false);
const [blueprint, setBlueprint] = useState<any>(null);
useEffect(() => {
if (siteId) {
loadPreviewData();
}
}, [siteId]);
const loadPreviewData = async () => {
try {
setLoading(true);
// Get the latest blueprint for this site
const blueprintsData = await fetchAPI(`/v1/site-builder/blueprints/?site=${siteId}`);
const blueprints = Array.isArray(blueprintsData?.results) ? blueprintsData.results : Array.isArray(blueprintsData) ? blueprintsData : [];
if (blueprints.length > 0) {
const latestBlueprint = blueprints[0];
setBlueprint(latestBlueprint);
// Get deployment record to find preview URL
if (latestBlueprint.deployed_version || latestBlueprint.status === 'deployed') {
try {
// 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 : [];
// 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
// Use VPS IP or configured URL for Sites Renderer
const sitesRendererUrl = import.meta.env.VITE_SITES_RENDERER_URL ||
(window as any).__SITES_RENDERER_URL__ ||
'http://31.97.144.105:8024';
setPreviewUrl(`${sitesRendererUrl}/${siteId}`);
}
} catch (error) {
console.warn('No deployment record found:', error);
// If blueprint is deployed but no deployment record, try Sites Renderer directly
if (latestBlueprint.status === 'deployed') {
// Use VPS IP or configured URL for Sites Renderer
const sitesRendererUrl = import.meta.env.VITE_SITES_RENDERER_URL ||
(window as any).__SITES_RENDERER_URL__ ||
'http://31.97.144.105:8024';
setPreviewUrl(`${sitesRendererUrl}/${siteId}`);
}
}
}
// 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) {
toast.error(`Failed to load preview: ${error.message}`);
} finally {
setLoading(false);
}
};
const handleRefresh = () => {
if (previewUrl) {
const iframe = document.getElementById('preview-iframe') as HTMLIFrameElement;
if (iframe) {
iframe.src = iframe.src;
}
}
};
const handleOpenInNewTab = () => {
if (previewUrl) {
window.open(previewUrl, '_blank');
}
};
if (loading) {
return (
<div className="p-6">
<PageMeta title="Site Preview" />
<div className="flex items-center justify-center h-64">
<div className="text-gray-500">Loading preview...</div>
</div>
</div>
);
}
if (!previewUrl) {
return (
<div className="p-6">
<PageMeta title="Site Preview - IGNY8" />
<div className="mb-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Site Preview
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Preview your deployed site
</p>
</div>
<Card className="p-12 text-center">
<p className="text-gray-600 dark:text-gray-400 mb-4">
{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 && (
<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>
);
}
return (
<div className={`p-6 ${isFullscreen ? 'fixed inset-0 z-50 bg-white dark:bg-gray-900 p-0' : ''}`}>
<PageMeta title="Site Preview - IGNY8" />
{!isFullscreen && (
<div className="mb-6 flex justify-between items-center">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Site Preview
</h1>
<p className="text-gray-600 dark:text-gray-400 mt-1">
Live preview of your deployed site
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={handleRefresh} startIcon={<RefreshCwIcon className="w-4 h-4" />}>
Refresh
</Button>
<Button variant="outline" onClick={handleOpenInNewTab} startIcon={<ExternalLinkIcon className="w-4 h-4" />}>
Open in New Tab
</Button>
<Button variant="outline" onClick={() => setIsFullscreen(true)} startIcon={<Maximize2Icon className="w-4 h-4" />}>
Fullscreen
</Button>
</div>
</div>
)}
{isFullscreen && (
<div className="absolute top-4 right-4 z-10">
<Button variant="outline" onClick={() => setIsFullscreen(false)} startIcon={<Minimize2Icon className="w-4 h-4" />}>
Exit Fullscreen
</Button>
</div>
)}
<Card className={`${isFullscreen ? 'h-full m-0 rounded-none' : ''} overflow-hidden`}>
<div className={`relative ${isFullscreen ? 'h-screen' : 'h-[calc(100vh-300px)]'} min-h-[600px]`}>
<iframe
id="preview-iframe"
src={previewUrl}
className="w-full h-full border-0"
title="Site Preview"
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals"
allow="fullscreen"
/>
{!isFullscreen && (
<div className="absolute bottom-4 right-4 bg-black/50 text-white px-3 py-1 rounded text-sm">
{previewUrl}
</div>
)}
</div>
</Card>
</div>
);
}