diff --git a/admin-spa/src/routes/Appearance/Pages/components/PageSettings.tsx b/admin-spa/src/routes/Appearance/Pages/components/PageSettings.tsx index eb76fad..32292e9 100644 --- a/admin-spa/src/routes/Appearance/Pages/components/PageSettings.tsx +++ b/admin-spa/src/routes/Appearance/Pages/components/PageSettings.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import React, { useState, useEffect, useRef } from 'react'; +import { api } from '@/lib/api'; import { __ } from '@/lib/i18n'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; @@ -13,7 +14,7 @@ import { SelectValue, } from '@/components/ui/select'; import { Switch } from '@/components/ui/switch'; -import { Settings, Eye, Smartphone, Monitor, ExternalLink } from 'lucide-react'; +import { Settings, Eye, Smartphone, Monitor, ExternalLink, RefreshCw, Loader2 } from 'lucide-react'; interface Section { id: string; @@ -40,6 +41,7 @@ interface AvailableSource { interface PageSettingsProps { page: PageItem | null; section: Section | null; + sections: Section[]; // All sections for preview onSectionUpdate: (section: Section) => void; isTemplate?: boolean; availableSources?: AvailableSource[]; @@ -111,11 +113,84 @@ const COLOR_SCHEMES = [ export function PageSettings({ page, section, + sections, onSectionUpdate, isTemplate = false, availableSources = [], }: PageSettingsProps) { - const [previewMode, setPreviewMode] = React.useState<'desktop' | 'mobile'>('desktop'); + const [previewMode, setPreviewMode] = useState<'desktop' | 'mobile'>('desktop'); + const [previewHtml, setPreviewHtml] = useState(null); + const [previewLoading, setPreviewLoading] = useState(false); + const [showPreview, setShowPreview] = useState(false); + const iframeRef = useRef(null); + const previewTimeoutRef = useRef(null); + + // Debounced preview fetch + useEffect(() => { + if (!page || !showPreview) return; + + // Clear existing timeout + if (previewTimeoutRef.current) { + clearTimeout(previewTimeoutRef.current); + } + + // Debounce preview updates + previewTimeoutRef.current = setTimeout(async () => { + setPreviewLoading(true); + try { + const endpoint = page.type === 'page' + ? `/preview/page/${page.slug}` + : `/preview/template/${page.cpt}`; + + const response = await api.post(endpoint, { sections }); + if (response?.html) { + setPreviewHtml(response.html); + } + } catch (error) { + console.error('Preview error:', error); + } finally { + setPreviewLoading(false); + } + }, 500); + + return () => { + if (previewTimeoutRef.current) { + clearTimeout(previewTimeoutRef.current); + } + }; + }, [page, sections, showPreview]); + + // Update iframe when HTML changes + useEffect(() => { + if (iframeRef.current && previewHtml) { + const doc = iframeRef.current.contentDocument; + if (doc) { + doc.open(); + doc.write(previewHtml); + doc.close(); + } + } + }, [previewHtml]); + + // Manual refresh + const handleRefreshPreview = async () => { + if (!page) return; + setPreviewLoading(true); + try { + const endpoint = page.type === 'page' + ? `/preview/page/${page.slug}` + : `/preview/template/${page.cpt}`; + + const response = await api.post(endpoint, { sections }); + if (response?.html) { + setPreviewHtml(response.html); + } + } catch (error) { + console.error('Preview error:', error); + } finally { + setPreviewLoading(false); + } + }; // Update section prop const updateProp = (name: string, value: any, isDynamic?: boolean) => { @@ -310,16 +385,30 @@ export function PageSettings({ )} - {/* Preview Toggle */} + {/* Preview Panel */} - - - {__('Preview')} + + + + {__('Preview')} + + {showPreview && ( + + )} -
+ {/* Preview Mode Toggle */} +
-

- {__('Live preview will be available after saving.')} -

+ + {/* Preview Toggle */} + {!showPreview ? ( + + ) : ( +
+ {/* Preview Iframe Container */} +
+ {previewLoading && ( +
+ +
+ )} +