diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx index af936b0b..319c0454 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/TemplatePanel.tsx @@ -22,7 +22,7 @@ import { export const CustomTemplateCard = React.memo(function CustomTemplateCard({ template }: { template: CustomTemplates }) { const router = useRouter(); - const { previewLayouts, loading, totalLayouts } = useCustomTemplatePreview(`${template.id}`); + const { previewLayouts, loading } = useCustomTemplatePreview(`${template.id}`); const handleOpen = useCallback(() => { trackEvent(MixpanelEvent.Templates_Custom_Opened, { template_id: template.id, template_name: template.name }); if (template.id.startsWith('custom-')) { @@ -38,7 +38,7 @@ export const CustomTemplateCard = React.memo(function CustomTemplateCard({ templ onClick={handleOpen} > - + { + const router = useRouter(); + + const [schemaEditorSlideIndex, setSchemaEditorSlideIndex] = useState(null); + const [schemaPreviewData, setSchemaPreviewData] = useState>>({}); + + const { selectedFile, handleFileSelect, removeFile } = useFileUpload(); + + + const { + state, + uploadedFonts, + slides, + setSlides, + completedSlides, + checkFonts, + uploadFont, + removeFont, + fontUploadAndPreview, + initTemplateCreation, + retrySlide, + } = useTemplateCreation(); + + // Layout saving hook + const { + isSavingLayout, + isModalOpen, + openSaveModal, + closeSaveModal, + saveLayout, + } = useLayoutSaving(slides); + + + useEffect(() => { + const existingScript = document.querySelector('script[src*="tailwindcss.com"]'); + if (!existingScript) { + const script = document.createElement("script"); + script.src = TAILWIND_CDN_URL; + script.async = true; + document.head.appendChild(script); + } + }, []); + + + /** + * Step 1: Check fonts in uploaded PPTX + */ + const handleCheckFonts = useCallback(async () => { + if (selectedFile) { + await checkFonts(selectedFile); + } + }, [selectedFile, checkFonts]); + + /** + * Step 2: Upload fonts and generate preview + */ + const handleFontUploadAndPreview = useCallback(async () => { + if (selectedFile) { + const data = await fontUploadAndPreview(selectedFile); + if (data) { + useFontLoader(data.fonts); + } + } + }, [selectedFile, fontUploadAndPreview]); + + /** + * Step 5: Save template with metadata + */ + const handleSaveTemplate = useCallback(async ( + layoutName: string, + description: string, + template_info_id: string + ): Promise => { + const id = await saveLayout(layoutName, description, template_info_id); + if (id) { + router.push(`/template-preview?slug=custom-${id}`); + } + return id; + }, [saveLayout, router]); + + /** + * Update a specific slide's data + */ + const handleSlideUpdate = useCallback((index: number, updatedSlideData: Partial) => { + setSlides((prevSlides) => + prevSlides.map((s, i) => + i === index + ? { ...s, ...updatedSlideData, modified: true } + : s + ) + ); + }, [setSlides]); + + + /** + * Open schema editor for a specific slide + */ + const handleOpenSchemaEditor = useCallback((index: number | null) => { + setSchemaEditorSlideIndex(index); + }, []); + + /** + * Close schema editor + */ + const handleCloseSchemaEditor = useCallback(() => { + setSchemaEditorSlideIndex(null); + }, []); + + /** + * Save changes from schema editor + */ + const handleSchemaEditorSave = useCallback((updatedReact: string) => { + if (schemaEditorSlideIndex !== null) { + setSlides(prev => prev.map((s, i) => + i === schemaEditorSlideIndex ? { ...s, react: updatedReact } : s + )); + } + setSchemaEditorSlideIndex(null); + }, [schemaEditorSlideIndex, setSlides]); + + /** + * Update schema preview content (for AI fill) + */ + const handleSchemaPreviewContent = useCallback((content: Record) => { + if (schemaEditorSlideIndex !== null) { + setSchemaPreviewData(prev => ({ + ...prev, + [schemaEditorSlideIndex]: content + })); + } + }, [schemaEditorSlideIndex]); + + /** + * Clear schema preview data for a specific slide + */ + const handleClearSchemaPreview = useCallback((slideIndex: number) => { + setSchemaPreviewData(prev => { + const newData = { ...prev }; + delete newData[slideIndex]; + return newData; + }); + }, []); + + + + const showFileUpload = state.step === 'file-upload'; + const showFontManager = state.step === 'font-check' || state.step === 'font-upload'; + const showPreview = state.step === 'slides-preview'; + const showSlides = state.step === 'template-creation' || state.step === 'completed'; + const isProcessingCompleted = state.step === 'completed'; + + + + return ( +
+ +
+ + {showFileUpload ? ( +
+ + +
+ ) : ( +
+ + + + {/* Step 2: Font Management */} + {showFontManager && ( + + )} + + {/* Step 3: Slide Preview */} + {showPreview && ( + + )} + + {/* Step 4: Template Creation & Editing */} + {showSlides && slides.length > 0 && ( + + )} + + {/* Floating Save Template Button */} + {isProcessingCompleted && slides.some((s) => s.processed) && ( + s.processing)} + /> + )} + + {/* Save Template Modal */} + +
+ )} + +
+ ); +}; + +export default CustomTemplatePage; diff --git a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx b/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx deleted file mode 100644 index 0a6447b2..00000000 --- a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import Header from "@/app/(presentation-generator)/(dashboard)/dashboard/components/Header"; - -export const APIKeyWarning: React.FC = () => { - return ( -
-
-
-
-

- Please add "GOOGLE_API_KEY" to enable template creation via AI. -

-

Please add your OpenAI API Key to process the layout

-

- This feature requires an OpenAI model GPT-5. Configure your key in settings or via environment variables. -

-
-
-
- ); -}; \ No newline at end of file diff --git a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/EachSlide/EditControls.tsx b/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/EachSlide/EditControls.tsx deleted file mode 100644 index 82f2f111..00000000 --- a/electron/servers/nextjs/app/(presentation-generator)/custom-template/components/EachSlide/EditControls.tsx +++ /dev/null @@ -1,167 +0,0 @@ -'use client' - -import React from "react"; -import { Button } from "@/components/ui/button"; -import { Textarea } from "@/components/ui/textarea"; -import { Pencil, Eraser, RotateCcw, SendHorizontal, X } from "lucide-react"; -import { EditControlsProps } from "../../types"; - -export const EditControls: React.FC = ({ - isEditMode, - prompt, - isUpdating, - strokeWidth, - strokeColor, - eraserMode, - onPromptChange, - onSave, - onCancel, - onStrokeWidthChange, - onStrokeColorChange, - onEraserModeChange, - onClearCanvas, -}) => { - const colors = [ - "#000000", - "#FF0000", - "#00FF00", - "#0000FF", - "#FFFF00", - "#FF00FF", - "#00FFFF", - "#FFA500", - ]; - - const strokeWidths = [1, 3, 5, 8, 12]; - - if (!isEditMode) return null; - - return ( -
- {/* Drawing Tools */} -
-
- {/* Drawing Tools */} -
- - - -
- - {/* Color Picker */} - {!eraserMode && ( -
- {colors.map((color) => ( -
- )} - - {/* Stroke Width */} -
- {strokeWidths.map((width) => ( - - ))} -
- - -
- - -
- - {/* Prompt Section */} -
- -
-