diff --git a/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx b/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx index ee02c096..46ba4f6d 100644 --- a/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/EditableLayoutWrapper.tsx @@ -34,18 +34,20 @@ const EditableLayoutWrapper: React.FC = ({ const [activeEditor, setActiveEditor] = useState(null); /** - * Recursively searches for image/icon data in the slide data structure + * Recursively searches for ALL image/icon data paths in the slide data structure */ - const findDataPath = (targetUrl: string, data: any, path: string = ''): { path: string; type: 'image' | 'icon'; data: any } | null => { - if (!data || typeof data !== 'object') return null; + const findAllDataPaths = (targetUrl: string, data: any, path: string = ''): { path: string; type: 'image' | 'icon'; data: any }[] => { + if (!data || typeof data !== 'object') return []; + + const matches: { path: string; type: 'image' | 'icon'; data: any }[] = []; // Check current level for __image_url__ or __icon_url__ if (data.__image_url__ && isMatchingUrl(data.__image_url__, targetUrl)) { - return { path, type: 'image', data }; + matches.push({ path, type: 'image', data }); } if (data.__icon_url__ && isMatchingUrl(data.__icon_url__, targetUrl)) { - return { path, type: 'icon', data }; + matches.push({ path, type: 'icon', data }); } // Recursively check nested objects and arrays @@ -54,16 +56,53 @@ const EditableLayoutWrapper: React.FC = ({ if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) { - const result = findDataPath(targetUrl, value[i], `${newPath}[${i}]`); - if (result) return result; + const results = findAllDataPaths(targetUrl, value[i], `${newPath}[${i}]`); + matches.push(...results); } } else if (value && typeof value === 'object') { - const result = findDataPath(targetUrl, value, newPath); - if (result) return result; + const results = findAllDataPaths(targetUrl, value, newPath); + matches.push(...results); } } - return null; + return matches; + }; + + /** + * Finds the best matching data path for a specific DOM element + */ + const findBestDataPath = (targetUrl: string, imgElement: HTMLImageElement, data: any): { path: string; type: 'image' | 'icon'; data: any } | null => { + const allMatches = findAllDataPaths(targetUrl, data); + + if (allMatches.length === 0) return null; + if (allMatches.length === 1) return allMatches[0]; + + // If multiple matches, use DOM position to find the correct one + const allImagesInContainer = containerRef.current?.querySelectorAll('img') || []; + const imgIndex = Array.from(allImagesInContainer).indexOf(imgElement); + + // Find images with the same URL pattern + const sameUrlImages: HTMLImageElement[] = []; + allImagesInContainer.forEach((img) => { + if (isMatchingUrl((img as HTMLImageElement).src, targetUrl)) { + sameUrlImages.push(img as HTMLImageElement); + } + }); + + const sameUrlIndex = sameUrlImages.indexOf(imgElement); + + // Try to match based on position in the same URL group + if (sameUrlIndex >= 0 && sameUrlIndex < allMatches.length) { + return allMatches[sameUrlIndex]; + } + + // Fallback: try to match based on overall DOM position + if (imgIndex >= 0 && imgIndex < allMatches.length) { + return allMatches[imgIndex]; + } + + // Last resort: return the first match + return allMatches[0]; }; /** @@ -81,30 +120,32 @@ const EditableLayoutWrapper: React.FC = ({ if (cleanUrl1 === cleanUrl2) return true; - // Handle app_data paths and placeholder URLs - if (url1.includes('/app_data/') || url2.includes('/app_data/') || - url1.includes('placeholder') || url2.includes('placeholder')) { + // Handle placeholder URLs - be more specific + if ((url1.includes('placeholder') && url2.includes('placeholder')) || + (url1.includes('/static/images/') && url2.includes('/static/images/'))) { + return url1 === url2; // Require exact match for placeholders + } + + // Handle app_data paths - be more specific about filename matching + if (url1.includes('/app_data/') || url2.includes('/app_data/')) { const getFilename = (path: string) => path.split('/').pop() || ''; const filename1 = getFilename(url1); const filename2 = getFilename(url2); - if (filename1 === filename2 && filename1 !== '') return true; + if (filename1 === filename2 && filename1 !== '' && filename1.length > 10) { // Ensure significant filename + return true; + } } - // Extract and compare filenames for other URLs + // Extract and compare filenames for other URLs - be more restrictive const getFilename = (path: string) => path.split('/').pop() || ''; const filename1 = getFilename(url1); const filename2 = getFilename(url2); - if (filename1 === filename2 && filename1 !== '') { + if (filename1 === filename2 && filename1 !== '' && filename1.length > 10) { // Ensure significant filename return true; } - // Check if one URL is contained in another (for partial matches) - if (url1.includes(url2) || url2.includes(url1)) { - return true; - } - - return false; + return false; // Remove the overly permissive substring matching }; /** @@ -121,7 +162,7 @@ const EditableLayoutWrapper: React.FC = ({ const src = htmlImg.src; if (src) { - const result = findDataPath(src, slideData); + const result = findBestDataPath(src, htmlImg, slideData); if (result) { const { path: dataPath, type, data } = result; @@ -129,6 +170,9 @@ const EditableLayoutWrapper: React.FC = ({ // Mark as processed to prevent re-processing htmlImg.setAttribute('data-editable-processed', 'true'); + // Add a unique identifier to help with debugging + htmlImg.setAttribute('data-editable-id', `${type}-${dataPath}-${index}`); + const editableElement: EditableElement = { id: `${type}-${dataPath}-${index}`, type, @@ -248,6 +292,8 @@ const EditableLayoutWrapper: React.FC = ({ */ const handleImageChange = (newImageUrl: string, prompt?: string) => { if (activeEditor && activeEditor.element) { + + // Update the DOM element immediately for visual feedback activeEditor.element.src = newImageUrl; @@ -259,10 +305,8 @@ const EditableLayoutWrapper: React.FC = ({ prompt: prompt || activeEditor.data?.__image_prompt__ || '' })); - setActiveEditor(null); } }; - /** * Handles icon change from IconsEditor */ @@ -279,7 +323,6 @@ const EditableLayoutWrapper: React.FC = ({ query: query || activeEditor.data?.__icon_query__ || '' })); - setActiveEditor(null); } }; @@ -305,8 +348,6 @@ const EditableLayoutWrapper: React.FC = ({ {/* Render IconsEditor when an icon is being edited */} {activeEditor && activeEditor.type === 'icon' && ( void; onIconChange?: (newIconUrl: string, query?: string) => void; } const IconsEditor = ({ - icon: initialIcon, icon_prompt, onClose, onIconChange, @@ -36,9 +31,7 @@ const IconsEditor = ({ icon_prompt?.[0] || "" ); const [loading, setLoading] = useState(true); - - - + const [isOpen, setIsOpen] = useState(true); // Search for icons when component opens useEffect(() => { @@ -59,7 +52,6 @@ const IconsEditor = ({ query, limit: 40, }); - console.log("icons search data", data); setIcons(data); } catch (error) { console.error("Error fetching icons:", error); @@ -79,11 +71,20 @@ const IconsEditor = ({ } }; + // Handle close with animation + const handleClose = () => { + setIsOpen(false); + // Delay the actual close to allow animation to complete + setTimeout(() => { + onClose?.(); + }, 300); // Match the Sheet animation duration + }; + return (
- onClose?.()}> + handleClose()}> { - // State management - const [image, setImage] = useState(initialImage); - const [previewImages, setPreviewImages] = useState([initialImage]); + const [previewImages, setPreviewImages] = useState(initialImage); const [prompt, setPrompt] = useState(""); const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); const [isUploading, setIsUploading] = useState(false); const [uploadError, setUploadError] = useState(null); const [uploadedImageUrl, setUploadedImageUrl] = useState(null); + const [isOpen, setIsOpen] = useState(true); // Focus point and object fit for image editing const [isFocusPointMode, setIsFocusPointMode] = useState(false); @@ -71,6 +65,7 @@ const ImageEditor = ({ properties[imageIdx].initialObjectFit) || "cover" ); + console.log("previewImages", previewImages); // Refs const imageRef = useRef(null); @@ -78,12 +73,19 @@ const ImageEditor = ({ const toolbarRef = useRef(null); const popoverContentRef = useRef(null); - // Update local state when initial image changes useEffect(() => { - setImage(initialImage); - setPreviewImages([initialImage]); + setPreviewImages(initialImage); }, [initialImage]); + // Handle close with animation + const handleClose = () => { + setIsOpen(false); + // Delay the actual close to allow animation to complete + setTimeout(() => { + onClose?.(); + }, 300); // Match the Sheet animation duration + }; + // Close toolbar when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -111,10 +113,11 @@ const ImageEditor = ({ * Handles image selection and calls the parent callback */ const handleImageChange = (newImage: string) => { - setImage(newImage); + if (onImageChange) { onImageChange(newImage, promptContent); + setPreviewImages(newImage); } }; @@ -188,7 +191,6 @@ const ImageEditor = ({ setError("Please enter a prompt"); return; } - console.log("prompt", prompt); try { setIsGenerating(true); setError(null); @@ -196,7 +198,7 @@ const ImageEditor = ({ prompt: prompt, }); - setPreviewImages(response.paths); + setPreviewImages(response); } catch (err) { console.error("Error in image generation", err); setError("Failed to generate image. Please try again."); @@ -257,7 +259,7 @@ const ImageEditor = ({
- onClose?.()}> + handleClose()}> {error}

}
- {isGenerating || previewImages.length === 0 + {isGenerating || !previewImages ? Array.from({ length: 4 }).map((_, index) => ( )) - : previewImages.map((image, index) => ( -
handleImageChange(image as string)} - className="aspect-[4/3] w-full overflow-hidden rounded-lg border cursor-pointer hover:border-blue-500 transition-colors" - > - {image && ( - {`Preview - )} -
- ))} + :
handleImageChange(previewImages)} + className="aspect-[4/3] w-full overflow-hidden rounded-lg border cursor-pointer hover:border-blue-500 transition-colors" + > + {previewImages && ( + {`Preview`} + )} +
+ }
diff --git a/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx b/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx deleted file mode 100644 index c07ca7a9..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/SmartEditableWrapper.tsx +++ /dev/null @@ -1,329 +0,0 @@ -"use client"; - -import React, { createContext, useContext, useRef, useEffect, ReactNode, useState } from 'react'; -import ReactDOM from 'react-dom'; -import { useDispatch } from 'react-redux'; -import { updateSlideImage, updateSlideIcon } from '../../../store/slices/presentationGeneration'; -import ImageEditor from './ImageEditor'; -import IconsEditor from './IconsEditor'; - -interface EditableElement { - type: 'image' | 'icon'; - element: HTMLElement; - dataPath?: string; - props: any; -} - -interface SmartEditableContextType { - slideIndex: number; - slideId: string; - isEditMode: boolean; - slideData: any; -} - -interface SmartEditableProviderProps { - children: ReactNode; - slideIndex: number; - slideId: string; - slideData: any; - isEditMode?: boolean; -} - -const SmartEditableContext = createContext(null); - -export const useSmartEditable = () => { - const context = useContext(SmartEditableContext); - if (!context) { - throw new Error('useSmartEditable must be used within SmartEditableProvider'); - } - return context; -}; - -export const SmartEditableProvider: React.FC = ({ - children, - slideIndex, - slideId, - slideData, - isEditMode = true, -}) => { - const containerRef = useRef(null); - const [editableElements, setEditableElements] = useState([]); - const [activeEditor, setActiveEditor] = useState<{ - type: 'image' | 'icon'; - element: HTMLElement; - props: any; - rect: DOMRect; - } | null>(null); - - const dispatch = useDispatch(); - - useEffect(() => { - if (!isEditMode || !containerRef.current || !slideData) return; - - const container = containerRef.current; - - const findEditableElements = () => { - const elements: EditableElement[] = []; - - console.log('🔍 Starting smart detection with slideData:', slideData); - - // Detect Images and Icons only (text is now handled by TiptapTextReplacer) - const detectEditableElementsFromData = (data: any, path: string = '') => { - if (!data || typeof data !== 'object') return; - - // Check for __image_url__ pattern - if (data.__image_url__) { - console.log(`📸 Found __image_url__ at ${path}:`, data.__image_url__); - const imgElement = findDOMElementByImageUrl(container, data.__image_url__); - if (imgElement) { - elements.push({ - type: 'image', - element: imgElement, - dataPath: path, - props: { - slideIndex, - initialImage: data.__image_url__, - promptContent: data.__image_prompt__ || '', - imageIdx: elements.filter(e => e.type === 'image').length, - onImageChange: (newImageUrl: string, prompt?: string) => { - console.log(`🖼️ Image changed at ${path}:`, newImageUrl); - dispatch(updateSlideImage({ - slideIndex, - dataPath: path, - imageUrl: newImageUrl, - prompt: prompt - })); - } - } - }); - console.log(`✅ Matched image to DOM element:`, imgElement); - } - } - - // Check for __icon_url__ pattern - if (data.__icon_url__) { - console.log(`🎯 Found __icon_url__ at ${path}:`, data.__icon_url__); - const imgElement = findDOMElementByImageUrl(container, data.__icon_url__); - if (imgElement) { - elements.push({ - type: 'icon', - element: imgElement, - dataPath: path, - props: { - slideIndex, - elementId: `icon-${path.replace(/[^\w]/g, '-')}`, - icon: data.__icon_url__, - index: elements.filter(e => e.type === 'icon').length, - backgroundColor: '#3B82F6', - hasBg: false, - icon_prompt: data.__icon_query__ ? [data.__icon_query__] : [], - onIconChange: (newIconUrl: string, query?: string) => { - console.log(`🎯 Icon changed at ${path}:`, newIconUrl); - dispatch(updateSlideIcon({ - slideIndex, - dataPath: path, - iconUrl: newIconUrl, - query: query - })); - } - } - }); - console.log(`✅ Matched icon to DOM element:`, imgElement); - } - } - - // Recursively scan nested objects and arrays - Object.keys(data).forEach(key => { - const value = data[key]; - const newPath = path ? `${path}.${key}` : key; - - if (Array.isArray(value)) { - value.forEach((item, index) => { - detectEditableElementsFromData(item, `${newPath}[${index}]`); - }); - } else if (value && typeof value === 'object') { - detectEditableElementsFromData(value, newPath); - } - }); - }; - - detectEditableElementsFromData(slideData); - console.log('🎉 Final detected elements:', elements); - setEditableElements(elements); - }; - - const findDOMElementByImageUrl = (container: HTMLElement, targetUrl: string): HTMLImageElement | null => { - const allImages = Array.from(container.getElementsByTagName('img')); - - for (const img of allImages) { - if (isMatchingImageUrl(img.src, targetUrl)) { - return img; - } - } - return null; - }; - - const isMatchingImageUrl = (domSrc: string, dataSrc: string): boolean => { - // Direct match - if (domSrc === dataSrc) return true; - - // Handle app_data paths - if (dataSrc.includes('/app_data/images/') || domSrc.includes('/app_data/images/')) { - const getFilename = (path: string) => path.split('/').pop() || ''; - return getFilename(domSrc) === getFilename(dataSrc); - } - - // Handle placeholder URLs - if (dataSrc.includes('placeholder') || domSrc.includes('placeholder')) { - return true; - } - - // Extract and compare filenames - const getFilename = (path: string) => path.split('/').pop() || ''; - return getFilename(domSrc) === getFilename(dataSrc) && getFilename(domSrc) !== ''; - }; - - // Set up event listeners after elements are found - const timer = setTimeout(() => { - findEditableElements(); - }, 500); - - return () => { - clearTimeout(timer); - }; - }, [slideIndex, slideId, slideData, isEditMode, dispatch]); - - // Set up event listeners when editableElements change - useEffect(() => { - if (!containerRef.current || editableElements.length === 0) return; - - const container = containerRef.current; - - const handleClick = (event: MouseEvent) => { - const target = event.target as HTMLElement; - - // Handle image/icon clicks only - if (target.tagName === 'IMG') { - const imgElement = target as HTMLImageElement; - const editableElement = editableElements.find(el => el.element === imgElement); - - if (editableElement && (editableElement.type === 'image' || editableElement.type === 'icon')) { - event.preventDefault(); - event.stopPropagation(); - - const rect = imgElement.getBoundingClientRect(); - setActiveEditor({ - type: editableElement.type, - element: imgElement, - props: editableElement.props, - rect - }); - } - } - }; - - const handleMouseEnter = (event: MouseEvent) => { - const target = event.target as HTMLElement; - - // Handle image/icon hover only - if (target.tagName === 'IMG') { - const imgElement = target as HTMLImageElement; - const isEditable = editableElements.some(el => el.element === imgElement); - - if (isEditable) { - imgElement.style.cursor = 'pointer'; - imgElement.style.filter = 'brightness(0.9)'; - imgElement.style.transition = 'filter 0.2s ease'; - } - } - }; - - const handleMouseLeave = (event: MouseEvent) => { - const target = event.target as HTMLElement; - - // Handle image/icon hover only - if (target.tagName === 'IMG') { - const imgElement = target as HTMLImageElement; - const isEditable = editableElements.some(el => el.element === imgElement); - - if (isEditable) { - imgElement.style.filter = ''; - } - } - }; - - container.addEventListener('click', handleClick); - container.addEventListener('mouseenter', handleMouseEnter, true); - container.addEventListener('mouseleave', handleMouseLeave, true); - - return () => { - container.removeEventListener('click', handleClick); - container.removeEventListener('mouseenter', handleMouseEnter, true); - container.removeEventListener('mouseleave', handleMouseLeave, true); - }; - }, [editableElements]); - - return ( - -
- {children} -
- - {/* Render active editor as a modal/overlay */} - {activeEditor && ( - setActiveEditor(null)} - /> - )} -
- ); -}; - -// Simple overlay component for editors -const EditorOverlay: React.FC<{ - activeEditor: { - type: 'image' | 'icon'; - element: HTMLElement; - props: any; - rect: DOMRect; - }; - onClose: () => void; -}> = ({ activeEditor, onClose }) => { - useEffect(() => { - const handleEscape = (e: KeyboardEvent) => { - if (e.key === 'Escape') onClose(); - }; - - const handleClickOutside = (e: MouseEvent) => { - // Close if clicked outside the editor - const target = e.target as HTMLElement; - if (!target.closest('.editor-modal')) { - onClose(); - } - }; - - document.addEventListener('keydown', handleEscape); - document.addEventListener('click', handleClickOutside); - - return () => { - document.removeEventListener('keydown', handleEscape); - document.removeEventListener('click', handleClickOutside); - }; - }, [onClose]); - - // Handle image/icon editing in modal - const EditorComponent = activeEditor.type === 'image' ? ImageEditor : IconsEditor; - - return ReactDOM.createPortal( -
-
- -
-
, - document.body - ); -}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx index 3cceb669..5482b702 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx @@ -166,7 +166,6 @@ const TiptapTextReplacer: React.FC = ({ currentElement = currentElement.parentElement; } - return false; }; diff --git a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx index 84b39f5b..29c5e29a 100644 --- a/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx +++ b/servers/nextjs/app/(presentation-generator)/hooks/useGroupLayouts.tsx @@ -60,7 +60,6 @@ export const useGroupLayouts = () => { isEditMode={isEditMode} layout={Layout} onContentChange={(content: string, dataPath: string, slideIndex?: number) => { - console.log(`Text content changed at slide ${slideIndex}, path ${dataPath}:`, content); // Dispatch Redux action to update slide content if (dataPath && slideIndex !== undefined) { diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx index 22ae5cea..08389f9e 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx @@ -21,10 +21,7 @@ const GenerateButton: React.FC = ({ const isDisabled = loadingState.isLoading || streamState.isLoading || - streamState.isStreaming || - !outlines || - outlines.length === 0 || - !selectedLayoutGroup; + streamState.isStreaming const getButtonText = () => { if (loadingState.isLoading) return loadingState.message; diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx index 97f8fe3c..9c615eda 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx @@ -31,7 +31,8 @@ const OutlinePage: React.FC = () => { const { loadingState, handleSubmit } = usePresentationGeneration( presentation_id, outlines, - selectedLayoutGroup + selectedLayoutGroup, + setActiveTab ); if (!presentation_id) { diff --git a/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts b/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts index 4bc05cfa..79a492ed 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/hooks/usePresentationGeneration.ts @@ -5,7 +5,7 @@ import { toast } from "@/hooks/use-toast"; import { clearPresentationData, setPresentationData, SlideOutline } from "@/store/slices/presentationGeneration"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import { useLayout } from "../../context/LayoutContext"; -import { LayoutGroup, LoadingState } from "../types/index"; +import { LayoutGroup, LoadingState, TABS } from "../types/index"; const DEFAULT_LOADING_STATE: LoadingState = { message: "", @@ -17,7 +17,8 @@ const DEFAULT_LOADING_STATE: LoadingState = { export const usePresentationGeneration = ( presentationId: string | null, outlines: SlideOutline[] | null, - selectedLayoutGroup: LayoutGroup | null + selectedLayoutGroup: LayoutGroup | null, + setActiveTab: (tab: string) => void ) => { const dispatch = useDispatch(); const router = useRouter(); @@ -69,7 +70,10 @@ export const usePresentationGeneration = ( }, [selectedLayoutGroup, getLayoutById]); const handleSubmit = useCallback(async () => { - + if (!selectedLayoutGroup) { + setActiveTab(TABS.LAYOUTS); + return; + } if (!validateInputs()) return; diff --git a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx index 5e218705..e1cb7b4d 100644 --- a/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/pdf-maker/PdfMakerPage.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useState, useCallback, useRef } from "react"; +import React, { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; import { RootState } from "@/store/store"; import { Skeleton } from "@/components/ui/skeleton"; @@ -8,30 +8,21 @@ import { Skeleton } from "@/components/ui/skeleton"; import { DashboardApi } from "@/app/dashboard/api/dashboard"; -import { - setPresentationData, -} from "@/store/slices/presentationGeneration"; import { toast } from "@/hooks/use-toast"; import { Button } from "@/components/ui/button"; import { AlertCircle } from "lucide-react"; -import { setThemeColors, ThemeColors } from "../store/themeSlice"; -import { ThemeType } from "../upload/type"; -import { renderSlideContent } from "../components/slide_config"; - +import { useGroupLayouts } from "../hooks/useGroupLayouts"; +import { setPresentationData } from "@/store/slices/presentationGeneration"; const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { - + const { renderSlideContent, loading } = useGroupLayouts(); + const [contentLoading, setContentLoading] = useState(true); const dispatch = useDispatch(); - const [loading, setLoading] = useState(true); - - const { currentTheme, currentColors } = useSelector( - (state: RootState) => state.theme - ); const { presentationData } = useSelector( (state: RootState) => state.presentationGeneration ); @@ -45,22 +36,8 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { const fetchUserSlides = async () => { try { const data = await DashboardApi.getPresentation(presentation_id); - if (data) { - if (data.presentation.theme) { - dispatch( - setThemeColors({ - ...data.presentation.theme.colors, - theme: data.presentation.theme.name as ThemeType, - }) - ); - setColorsVariables( - data.presentation.theme.colors, - data.presentation.theme.name as ThemeType - ); - } - dispatch(setPresentationData(data)); - setLoading(false); - } + dispatch(setPresentationData(data)); + setContentLoading(false); } catch (error) { setError(true); toast({ @@ -68,19 +45,11 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { description: "Failed to load presentation", variant: "destructive", }); - console.error("Error fetching user slides:", error); - setLoading(false); + setContentLoading(false); } }; - const setColorsVariables = (colors: ThemeColors, theme: ThemeType) => { - const root = document.documentElement; - Object.entries(colors).forEach(([key, value]) => { - const cssKey = key.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()); - root.style.setProperty(`--${theme}-${cssKey}`, value); - }); - }; - const language = presentationData?.presentation?.language || "English"; + console.log("presentationData", presentationData); // Regular view return (
@@ -109,15 +78,14 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { ) : ( -
+
{!presentationData || loading || + contentLoading || !presentationData?.slides || presentationData?.slides.length === 0 ? (
@@ -136,10 +104,10 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { {presentationData && presentationData.slides && presentationData.slides.length > 0 && - presentationData.slides.map((slide, index) => ( + presentationData.slides.map((slide: any, index: number) => (
- {renderSlideContent(slide, language)} + {renderSlideContent(slide, false)}
))} diff --git a/servers/nextjs/app/dashboard/components/PresentationCard.tsx b/servers/nextjs/app/dashboard/components/PresentationCard.tsx index e978ad72..b1497c11 100644 --- a/servers/nextjs/app/dashboard/components/PresentationCard.tsx +++ b/servers/nextjs/app/dashboard/components/PresentationCard.tsx @@ -52,7 +52,6 @@ export const PresentationCard = ({ description: "The presentation has been deleted successfully", variant: "default", }); - // Call the onDeleted callback to update the parent state if (onDeleted) { onDeleted(id); } @@ -63,7 +62,6 @@ export const PresentationCard = ({ variant: "destructive", }); } - // Removed window.location.reload() - no longer needed }; diff --git a/servers/nextjs/store/slices/presentationGeneration.ts b/servers/nextjs/store/slices/presentationGeneration.ts index 50578a2e..4494076e 100644 --- a/servers/nextjs/store/slices/presentationGeneration.ts +++ b/servers/nextjs/store/slices/presentationGeneration.ts @@ -69,9 +69,6 @@ const presentationGenerationSlice = createSlice({ }, // Clear presentation data clearPresentationData: (state) => { - state.presentation_id = null; - state.error = null; - state.isLoading = false; state.presentationData = null; }, clearOutlines: (state) => { @@ -238,17 +235,23 @@ const presentationGenerationSlice = createSlice({ // Set the image properties const finalKey = keys[keys.length - 1]; + const target = isNaN(Number(finalKey)) ? current[finalKey] : current[Number(finalKey)]; + + // Preserve existing properties if the target already exists + const updatedValue = { + ...(target && typeof target === 'object' ? target : {}), + __image_url__: url, + __image_prompt__: promptText || (target?.__image_prompt__) || '' + }; + if (isNaN(Number(finalKey))) { - current[finalKey] = { - __image_url__: url, - __image_prompt__: promptText || '' - }; + current[finalKey] = updatedValue; } else { - current[Number(finalKey)] = { - __image_url__: url, - __image_prompt__: promptText || '' - }; + current[Number(finalKey)] = updatedValue; } + + // Add debugging + console.log('Redux: Updated slide image at path:', path, 'with URL:', url); }; // Update the slide image @@ -308,17 +311,23 @@ const presentationGenerationSlice = createSlice({ // Set the icon properties const finalKey = keys[keys.length - 1]; + const target = isNaN(Number(finalKey)) ? current[finalKey] : current[Number(finalKey)]; + + // Preserve existing properties if the target already exists + const updatedValue = { + ...(target && typeof target === 'object' ? target : {}), + __icon_url__: url, + __icon_query__: queryText || (target?.__icon_query__) || '' + }; + if (isNaN(Number(finalKey))) { - current[finalKey] = { - __icon_url__: url, - __icon_query__: queryText || '' - }; + current[finalKey] = updatedValue; } else { - current[Number(finalKey)] = { - __icon_url__: url, - __icon_query__: queryText || '' - }; + current[Number(finalKey)] = updatedValue; } + + // Add debugging + console.log('Redux: Updated slide icon at path:', path, 'with URL:', url); }; // Update the slide icon