diff --git a/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialog.tsx b/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialog.tsx index d093fd5..c706dc4 100644 --- a/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialog.tsx +++ b/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialog.tsx @@ -59,6 +59,7 @@ export const PageManagementDialog: React.FC = ({ open const [editingPageId, setEditingPageId] = useState(null); const [editingPageName, setEditingPageName] = useState(''); const [error, setError] = useState(null); + const [autoSaving, setAutoSaving] = useState(null); // Track which page is being auto-saved // Reset state when dialog opens React.useEffect(() => { @@ -67,6 +68,7 @@ export const PageManagementDialog: React.FC = ({ open setEditingPageId(null); setEditingPageName(''); setError(null); + setAutoSaving(null); } }, [open]); @@ -135,12 +137,40 @@ export const PageManagementDialog: React.FC = ({ open // Handle publishing/unpublishing a page const handlePublishPage = async (pageId: string, publish: boolean) => { + console.log('🚀 handlePublishPage called:', { pageId, publish, currentPageId: currentPage?.id }); + try { - await publishPage(pageId, publish); setError(null); + + // Auto-save if publishing the current page (always save to ensure latest changes are published) + if (publish && currentPage && pageId === currentPage.id) { + console.log('🔄 Auto-saving page before publishing...', { pageId, currentPageId: currentPage.id }); + setAutoSaving(pageId); + + try { + console.log('💾 Calling savePage...'); + await savePage(pageId); + console.log('✅ Auto-save completed successfully'); + } catch (saveErr) { + console.error('❌ Auto-save failed:', saveErr); + setAutoSaving(null); + setError('Failed to auto-save page before publishing. Please save manually first.'); + return; // Don't proceed with publishing if auto-save fails + } + + setAutoSaving(null); + } else { + console.log('⏭️ Skipping auto-save:', { publish, hasCurrentPage: !!currentPage, isCurrentPage: pageId === currentPage?.id }); + } + + // Proceed with publish/unpublish + console.log('📤 Calling publishPage...'); + await publishPage(pageId, publish); + console.log('✅ Publish completed successfully'); } catch (err) { + console.error('❌ Publish failed:', err); + setAutoSaving(null); setError(`Failed to ${publish ? 'publish' : 'unpublish'} page`); - console.error(err); } }; @@ -297,6 +327,9 @@ export const PageManagementDialog: React.FC = ({ open ) : ( {page.name} + {autoSaving === page.id && ( + + )} {page.is_local && ( )} @@ -312,7 +345,7 @@ export const PageManagementDialog: React.FC = ({ open Route: {page.route || 'None'} - Last Updated: {formatDate(page.updated_at as string)} + Last Updated: {formatDate((page as any).updated_at)} {page.backup_date && ( @@ -367,23 +400,30 @@ export const PageManagementDialog: React.FC = ({ open {/* Publish/Unpublish Button */} {!page.is_local && ( - - { - e.stopPropagation(); - handlePublishPage(page.id, !page.is_published); - }} - size="small" - color={page.is_published ? "success" : "primary"} - > - {page.is_published ? ( - - ) : ( - - )} - + + + { + e.stopPropagation(); + handlePublishPage(page.id, !page.is_published); + }} + size="small" + color={page.is_published ? "success" : "primary"} + disabled={autoSaving === page.id} + > + {page.is_published ? ( + + ) : ( + + )} + + )} diff --git a/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialogAdapter.tsx b/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialogAdapter.tsx index 102095e..7376a03 100644 --- a/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialogAdapter.tsx +++ b/frontend/src/features/plugin-studio/components/dialogs/PageManagementDialogAdapter.tsx @@ -12,17 +12,21 @@ interface PageManagementDialogAdapterProps { * @param props The component props * @returns The page management dialog adapter component */ -export const PageManagementDialog: React.FC = ({ - open, - onClose +export const PageManagementDialog: React.FC = ({ + open, + onClose }) => { - const { + const { currentPage, publishPage, + savePage, backupPage, restorePage, updatePage } = usePluginStudio(); + + console.log('🔧 PageManagementDialogAdapter rendered:', { open, hasCurrentPage: !!currentPage }); + console.log('🔧 PageManagementDialogAdapter context:', { currentPageId: currentPage?.id, currentPageName: currentPage?.name }); // Only render the dialog if we have a current page if (!currentPage) { @@ -31,7 +35,33 @@ export const PageManagementDialog: React.FC = // Handle publishing a page const handlePublish = async (pageId: string, publish: boolean) => { - await publishPage(pageId, publish); + console.log('🚀 PageManagementDialogAdapter handlePublish called:', { pageId, publish, currentPageId: currentPage?.id }); + + try { + // Auto-save if publishing the current page (always save to ensure latest changes are published) + if (publish && currentPage && pageId === currentPage.id) { + console.log('🔄 Auto-saving page before publishing...', { pageId, currentPageId: currentPage.id }); + + try { + console.log('💾 Calling savePage...'); + await savePage(pageId); + console.log('✅ Auto-save completed successfully'); + } catch (saveErr) { + console.error('❌ Auto-save failed:', saveErr); + throw new Error('Failed to auto-save page before publishing. Please save manually first.'); + } + } else { + console.log('⏭️ Skipping auto-save:', { publish, hasCurrentPage: !!currentPage, isCurrentPage: pageId === currentPage?.id }); + } + + // Proceed with publish/unpublish + console.log('📤 Calling publishPage...'); + await publishPage(pageId, publish); + console.log('✅ Publish completed successfully'); + } catch (err) { + console.error('❌ Publish failed:', err); + throw err; + } }; // Handle backing up a page diff --git a/frontend/src/features/plugin-studio/components/grid-toolbar/GridToolbar.tsx b/frontend/src/features/plugin-studio/components/grid-toolbar/GridToolbar.tsx index b32a8b8..4765b9c 100644 --- a/frontend/src/features/plugin-studio/components/grid-toolbar/GridToolbar.tsx +++ b/frontend/src/features/plugin-studio/components/grid-toolbar/GridToolbar.tsx @@ -124,19 +124,19 @@ export const GridToolbar: React.FC = ({ onSave }) => { }}> {/* Left Section: Basic Actions and Page Management Controls */} + {/* Page Selector - Always visible */} + + {currentPage ? ( <> - {/* Create Page Button */} - - createPage('New Page')} - size="small" - sx={{ color: 'primary.main' }} - > - - - - {/* Save Page Button */} = ({ onSave }) => { - {/* Page Selector */} - - {/* Page Management and Route Management */} {currentPage.is_local !== true && ( <> @@ -252,17 +241,8 @@ export const GridToolbar: React.FC = ({ onSave }) => { )} ) : ( - - - createPage('New Page')} - size="small" - sx={{ color: 'primary.main' }} - > - - - - No pages available. Create a new page to get started. + + No pages available. Use the + button to create a new page. )} diff --git a/frontend/src/features/plugin-studio/hooks/page/usePages.ts b/frontend/src/features/plugin-studio/hooks/page/usePages.ts index 878f43a..8cc81a3 100644 --- a/frontend/src/features/plugin-studio/hooks/page/usePages.ts +++ b/frontend/src/features/plugin-studio/hooks/page/usePages.ts @@ -337,8 +337,8 @@ export const usePages = () => { // Normal case - update existing page // Create deep clones to avoid reference issues const content = { - layouts: JSON.parse(JSON.stringify(currentPage.layouts)), - modules: JSON.parse(JSON.stringify(currentPage.modules)) + layouts: JSON.parse(JSON.stringify(currentPage.layouts || {})), + modules: JSON.parse(JSON.stringify(currentPage.modules || {})) }; console.log('savePage - Saving content to backend:', content); diff --git a/frontend/src/features/unified-dynamic-page-renderer/adapters/PluginStudioAdapter.tsx b/frontend/src/features/unified-dynamic-page-renderer/adapters/PluginStudioAdapter.tsx index ac6b73b..0b03166 100644 --- a/frontend/src/features/unified-dynamic-page-renderer/adapters/PluginStudioAdapter.tsx +++ b/frontend/src/features/unified-dynamic-page-renderer/adapters/PluginStudioAdapter.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import { Box, useTheme } from '@mui/material'; import { UnifiedPageRenderer } from '../components/UnifiedPageRenderer'; import { ResponsiveContainer } from '../components/ResponsiveContainer'; @@ -76,6 +76,9 @@ export const PluginStudioAdapter: React.FC = ({ const [conversionError, setConversionError] = useState(null); const [isConverting, setIsConverting] = useState(false); + // Safeguard to prevent infinite loops during module updates + const isUpdatingModulesRef = useRef(false); + // Performance tracking const [performanceMetrics, setPerformanceMetrics] = useState<{ conversionTime?: number; @@ -415,9 +418,11 @@ export const PluginStudioAdapter: React.FC = ({ /** * Watch for changes in page modules and update converted data + * Fixed: Removed convertedPageData from dependency array to prevent infinite loop + * Added safeguard to prevent recursive updates */ useEffect(() => { - if (!page || !convertedPageData) return; + if (!page || !convertedPageData || isUpdatingModulesRef.current) return; // Check if modules have changed const currentModulesHash = JSON.stringify(page.modules); @@ -428,15 +433,18 @@ export const PluginStudioAdapter: React.FC = ({ if (currentModulesHash !== convertedModulesHash) { console.log('[PluginStudioAdapter] Modules changed, updating converted data'); + isUpdatingModulesRef.current = true; try { const updated = convertPageData(page, layouts || { desktop: [], tablet: [], mobile: [] }); setConvertedPageData(updated); } catch (error) { console.error('[PluginStudioAdapter] Module update failed:', error); setConversionError(error as Error); + } finally { + isUpdatingModulesRef.current = false; } } - }, [page?.modules, convertPageData, convertedPageData, layouts]); + }, [page?.modules, convertPageData, layouts]); /** * Determine render mode based on preview state