diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx index 5fbee460..1ad7983a 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx @@ -175,7 +175,7 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx index 84ea55de..d18db184 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx @@ -13,19 +13,20 @@ const SettingSideBar = ({ mode, setMode, selectedProvider, setSelectedProvider }

FILTER BY:

Select Mode

-
- + >Presenton +
- -
} { mode === 'nanobanana' &&
- - )}
- - - {/* Model Selection - only show if models are available */} - {selectedProvider !== 'codex' && modelsChecked && availableModels.length > 0 ? ( -
-
- -
- - - - - + {/* Model Selection - only show if models are available */} + {selectedProvider !== 'codex' && modelsChecked && availableModels.length > 0 ? ( +
+
+ +
+ + + + + + + + + No model found. + + {availableModels.map((model, index) => ( + { + if (currentModelField) { + onInputChange(value, currentModelField); + } + setOpenModelSelect(false); + }} + > + +
+
+
+ + {model} +
- - ))} - - - - - -
+
+ + ))} + + + + +
- ) : null} -
+
+ ) : null}
{/* Show message if no models found */} @@ -526,7 +520,7 @@ const TextProvider = ({ )} - {/* Web Grounding Toggle - show at the end, below models dropdown */} +
@@ -536,7 +530,6 @@ const TextProvider = ({

-
- -
- {/*
*/}
- -
- -
) } diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/loading.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/loading.tsx index 04f33049..114cde8b 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/loading.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/loading.tsx @@ -1,76 +1,113 @@ -import { Card } from "@/components/ui/card"; - -export default function LoadingProfile() { +function Shimmer({ className }: { className?: string }) { return ( -
- {/* Header Skeleton */} -
-
-
-
-
-
-
+
+ ); +} + +export default function LoadingSettings() { + return ( +
+
+ +
+ {/* SettingSideBar structure */} +
+
+ +
+
+ +
+ + +
+ +
+ {[0, 1].map((i) => ( +
+ + +
+ ))} +
+
+
+ +
+ +
-
- {/* Main Content Skeleton */} -
-
- {/* LLM Selection Content Skeleton */} -
- {/* Page Title */} -
-
-
+ {/* Main column — matches SettingPage + TextProvider default */} +
+
+
+ +
+
- {/* LLM Provider Cards */} -
- {[...Array(3)].map((_, index) => ( - -
-
-
-
-
-
-
-
-
-
- - {/* Configuration Fields */} -
- {[...Array(2)].map((_, fieldIndex) => ( -
-
-
-
- ))} -
- - ))} -
- - {/* Model Selection */} - -
-
-
+
+ {/* TextProvider top card: white panel, icon + copy left, controls right */} +
+
+ + + +
- +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+
+ + {/* TextProvider “Advanced” card */} +
+
+ + +
+
+ + +
+
- {/* Fixed Bottom Button Skeleton */} -
-
-
-
+ {/* Fixed save button — matches SettingPage placement */} +
+
); diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/CreateCustomTemplate.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/CreateCustomTemplate.tsx index 93f2a487..2c2d59b4 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/CreateCustomTemplate.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/templates/components/CreateCustomTemplate.tsx @@ -11,7 +11,7 @@ const CreateCustomTemplate = () => { trackEvent(MixpanelEvent.Templates_Build_Template_Clicked); router.push('/custom-template') }} - className='w-full rounded-xl border border-[#EDEEEF] cursor-pointer font-syne'> + className='w-full rounded-[22px] border border-[#EDEEEF] cursor-pointer font-syne'>
{
-
+
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 ca6f9671..af936b0b 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 @@ -2,20 +2,24 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter } from "next/navigation"; import { Card } from "@/components/ui/card"; -import { ArrowUpRight, ChevronRight, ExternalLink, Loader2, Plus } from "lucide-react"; +import { ArrowUpRight, ChevronRight, Loader2 } from "lucide-react"; import { templates } from "@/app/presentation-templates"; -import { TemplateWithData, TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils"; +import { TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils"; import { useCustomTemplateSummaries, useCustomTemplatePreview, CustomTemplates, } from "@/app/hooks/useCustomTemplates"; -import { CompiledLayout } from "@/app/hooks/compileLayout"; import CreateCustomTemplate from "./CreateCustomTemplate"; import Link from "next/link"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; +import { + TemplatePreviewStage, + LayoutsBadge, + InbuiltTemplatePreview, + CustomTemplatePreview, +} from "../../../components/TemplatePreviewComponents"; -// Component for rendering custom template card with lazy-loaded previews export const CustomTemplateCard = React.memo(function CustomTemplateCard({ template }: { template: CustomTemplates }) { const router = useRouter(); const { previewLayouts, loading, totalLayouts } = useCustomTemplatePreview(`${template.id}`); @@ -26,73 +30,29 @@ export const CustomTemplateCard = React.memo(function CustomTemplateCard({ templ } else { router.push(`/template-preview?slug=custom-${template.id}`) } - } - , [router, template.id, template.name]); + }, [router, template.id, template.name]); return ( - - - - {totalLayouts} {totalLayouts === 1 ? 'Layout' : 'Layouts'} - -
- - {/* Layout previews */} -
- {loading ? ( - // Loading placeholders - [...Array(Math.min(4, template.layoutCount))].map((_, index) => ( -
- -
- )) - ) : previewLayouts.length > 0 && ( - // Actual layout previews - previewLayouts.slice(0, 4).map((layout: CompiledLayout, index: number) => { - const LayoutComponent = layout.component; - return ( -
-
-
- -
-
- ); - }) - )} -
- - -
-
-

- {template.name} -

- -
- - -
+ + + + +
+

{template.name}

+
); }, (prev, next) => { - // Custom templates may be refetched, producing new object references; compare on fields we render/use. return ( - prev.template.id === next.template.id && prev.template.id === next.template.id && prev.template.name === next.template.name && prev.template.layoutCount === next.template.layoutCount @@ -106,54 +66,24 @@ const InbuiltTemplateCard = React.memo(function InbuiltTemplateCard({ template: TemplateLayoutsWithSettings; onOpen: (id: string) => void; }) { - const previewLayouts = useMemo(() => template.layouts.slice(0, 4), [template.layouts]); const handleOpen = useCallback(() => onOpen(template.id), [onOpen, template.id]); return ( - - {template.layouts.length} {template.layouts.length === 1 ? 'Layout' : 'Layouts'} - - -
-
- {previewLayouts.map((layout: TemplateWithData, index: number) => { - const LayoutComponent = layout.component; - return ( -
-
-
- -
-
- ); - })} -
-
-
-
- -

- {template.name} -

-

- {template.description} -

-
-
- - + + + + +
+
+

{template.name}

+

{template.description}

+
); @@ -254,7 +184,7 @@ const LayoutPreview = () => { {/* Inbuilt Templates Section: non-neo first, then Report (neo) */} {tab === 'default' && (
-
+
{nonNeoInbuilt.map((template) => ( {

Report

-
+
{neoInbuilt.map((template) => ( { Loading custom templates...
) : ( -
+
{customTemplateCards}
diff --git a/electron/servers/nextjs/app/(presentation-generator)/components/TemplatePreviewComponents.tsx b/electron/servers/nextjs/app/(presentation-generator)/components/TemplatePreviewComponents.tsx new file mode 100644 index 00000000..7a3f0bd2 --- /dev/null +++ b/electron/servers/nextjs/app/(presentation-generator)/components/TemplatePreviewComponents.tsx @@ -0,0 +1,125 @@ +"use client"; +import React, { memo, useMemo } from "react"; +import { Loader2 } from "lucide-react"; +import { TemplateWithData } from "@/app/presentation-templates/utils"; +import { CompiledLayout } from "@/app/hooks/compileLayout"; + + + + +export function TemplatePreviewStage({ children }: { children: React.ReactNode }) { + return ( +
+ + {children} +
+ ); +} + +export const LayoutsBadge = memo(function LayoutsBadge({ count }: { count: number }) { + return ( + + Layouts-{count} + + ); +}); + +export const ScaledSlidePreview = memo(function ScaledSlidePreview({ + children, + id, + index, + isOutline = false, +}: { + children: React.ReactNode; + id: string; + index: number; + isOutline?: boolean; +}) { + const PREVIEW_SCALE = isOutline ? 0.2 : 0.24; + const SLIDE_HEIGHT = 720 * PREVIEW_SCALE; + const SLIDE_WIDTH = 1280; + const SLIDE_NATIVE_HEIGHT = 720; + return ( +
+
+ {children} +
+
+ ); +}); + +export const InbuiltTemplatePreview = memo(function InbuiltTemplatePreview({ + layouts, + templateId, + isOutline = false, +}: { + layouts: TemplateWithData[]; + templateId: string; + isOutline?: boolean; +}) { + const previewLayouts = useMemo(() => layouts.slice(0, 2), [layouts]); + return ( +
+ {previewLayouts.map((layout, index) => { + const LayoutComponent = layout.component; + return ( + + + + ); + })} +
+ ); +}); + +export const CustomTemplatePreview = memo(function CustomTemplatePreview({ + previewLayouts, + loading, + templateId, + isOutline = false, +}: { + previewLayouts: CompiledLayout[]; + loading: boolean; + templateId: string; + isOutline?: boolean; +}) { + return ( +
+ {loading ? ( + [...Array(2)].map((_, index) => ( +
+ +
+ )) + ) : ( + previewLayouts.slice(0, 2).map((layout, index) => { + const LayoutComponent = layout.component; + return ( + + + + ); + }) + )} +
+ ); +}); diff --git a/electron/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx b/electron/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx index 6ae8c5c9..89daa4da 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/outline/components/CustomTemplateCard.tsx @@ -1,94 +1,50 @@ "use client"; import React, { memo } from "react"; import { Card } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; import { CustomTemplates, useCustomTemplatePreview } from "@/app/hooks/useCustomTemplates"; -import { Loader2 } from "lucide-react"; -import { CompiledLayout } from "@/app/hooks/compileLayout"; +import { + TemplatePreviewStage, + LayoutsBadge, + CustomTemplatePreview, +} from "../../components/TemplatePreviewComponents"; -// Memoized preview component to prevent re-renders during scroll -export const LayoutPreview = memo(({ layout, templateId, index }: { layout: CompiledLayout, templateId: string, index: number }) => { - const LayoutComponent = layout.component; - return ( -
-
-
- -
-
- ); -}); -LayoutPreview.displayName = 'LayoutPreview'; - -export const CustomTemplateCard = memo(({ template, onSelectTemplate, selectedTemplate }: { template: CustomTemplates, onSelectTemplate: (template: string) => void, selectedTemplate: string | null }) => { - - const { previewLayouts, loading: customLoading, totalLayouts } = useCustomTemplatePreview(template.id); +export const CustomTemplateCard = memo(function CustomTemplateCard({ + template, + onSelectTemplate, + selectedTemplate, +}: { + template: CustomTemplates; + onSelectTemplate: (template: string) => void; + selectedTemplate: string | null; +}) { + const { previewLayouts, loading, totalLayouts } = useCustomTemplatePreview(template.id); const isSelected = selectedTemplate === template.id; return ( - onSelectTemplate(template.id)} > - - - - Layouts- {totalLayouts} - -
- - {/* Layout previews */} -
- {customLoading ? ( - // Loading placeholders - [...Array(Math.min(4, template.layoutCount))].map((_, index) => ( -
- -
- )) - ) : previewLayouts.length > 0 && ( - // Actual layout previews - previewLayouts.slice(0, 4).map((layout: CompiledLayout, index: number) => { - const LayoutComponent = layout.component; - return ( -
-
-
- -
-
- ); - }) - )} -
- - -
-
-

+ + + + +
+

{template.name}

- -
); }); -CustomTemplateCard.displayName = 'CustomTemplateCard'; - diff --git a/electron/servers/nextjs/app/(presentation-generator)/outline/components/TemplateSelection.tsx b/electron/servers/nextjs/app/(presentation-generator)/outline/components/TemplateSelection.tsx index 5afe9425..4bf94643 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/outline/components/TemplateSelection.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/outline/components/TemplateSelection.tsx @@ -4,72 +4,49 @@ import React, { useEffect, useMemo, useCallback, memo } from "react"; import { TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils"; import { templates } from "@/app/presentation-templates"; import { Card } from "@/components/ui/card"; -import { TemplateWithData } from "@/app/presentation-templates/utils"; +import { cn } from "@/lib/utils"; import { CustomTemplates, useCustomTemplateSummaries } from "@/app/hooks/useCustomTemplates"; import { Loader2 } from "lucide-react"; -import { CustomTemplateCard } from "./CustomTemplateCard"; + import CreateCustomTemplate from "../../(dashboard)/templates/components/CreateCustomTemplate"; +import { CustomTemplateCard } from "./CustomTemplateCard"; +import { + TemplatePreviewStage, + LayoutsBadge, + InbuiltTemplatePreview, +} from "../../components/TemplatePreviewComponents"; -// Memoized layout preview for built-in templates -const BuiltInLayoutPreview = memo(({ layout, templateId, index }: { - layout: TemplateWithData; - templateId: string; - index: number; -}) => { - const LayoutComponent = layout.component; - return ( -
-
-
- -
-
- ); -}); -BuiltInLayoutPreview.displayName = 'BuiltInLayoutPreview'; - -// Memoized built-in template card -const BuiltInTemplateCard = memo(({ template, isSelected, onSelect }: { +const BuiltInTemplateCard = memo(function BuiltInTemplateCard({ + template, + isSelected, + onSelect, +}: { template: TemplateLayoutsWithSettings; isSelected: boolean; onSelect: (template: TemplateLayoutsWithSettings) => void; -}) => { - const previewLayouts = useMemo(() => template.layouts.slice(0, 4), [template.layouts]); +}) { const handleClick = useCallback(() => onSelect(template), [onSelect, template]); return ( - - {template.layouts.length} {template.layouts.length === 1 ? 'Layout' : 'Layouts'} - - -
-
- {previewLayouts.map((layout: TemplateWithData, index: number) => ( - - ))} -
-
-
-
+ + + + +
+

{template.name}

-

+

{template.description}

@@ -77,17 +54,16 @@ const BuiltInTemplateCard = memo(({ template, isSelected, onSelect }: { ); }); -BuiltInTemplateCard.displayName = 'BuiltInTemplateCard'; interface TemplateSelectionProps { selectedTemplate: (TemplateLayoutsWithSettings | string) | null; onSelectTemplate: (template: TemplateLayoutsWithSettings | string) => void; } -const TemplateSelection: React.FC = memo(({ +const TemplateSelection: React.FC = memo(function TemplateSelection({ selectedTemplate, - onSelectTemplate -}) => { + onSelectTemplate, +}) { useEffect(() => { const existingScript = document.querySelector( 'script[src*="tailwindcss.com"]' @@ -102,50 +78,44 @@ const TemplateSelection: React.FC = memo(({ const { templates: customTemplates, loading: customLoading } = useCustomTemplateSummaries(); - // Stable callback for custom template selection const handleCustomSelect = useCallback( (template: TemplateLayoutsWithSettings | string) => onSelectTemplate(template), [onSelectTemplate] ); - // Stable callback for built-in template selection const handleBuiltInSelect = useCallback( (template: TemplateLayoutsWithSettings) => onSelectTemplate(template), [onSelectTemplate] ); - // Derive the selected custom template id only when selectedTemplate changes const selectedCustomId = useMemo( - () => (typeof selectedTemplate === 'string' ? selectedTemplate : null), + () => (typeof selectedTemplate === "string" ? selectedTemplate : null), [selectedTemplate] ); - // Derive the selected built-in template id only when selectedTemplate changes const selectedBuiltInId = useMemo( - () => (typeof selectedTemplate !== 'string' ? selectedTemplate?.id ?? null : null), + () => (typeof selectedTemplate !== "string" ? selectedTemplate?.id ?? null : null), [selectedTemplate] ); - // Memoize the custom templates section const customTemplateCards = useMemo(() => { if (customLoading) { return (
- + Loading custom templates...
); } if (customTemplates.length === 0) { return ( -
- +
); } return ( -
+
{customTemplates.map((template: CustomTemplates) => ( = memo(({ ); }, [customLoading, customTemplates, handleCustomSelect, selectedCustomId]); - // Memoize the built-in templates list const builtInTemplateCards = useMemo( () => templates.map((template: TemplateLayoutsWithSettings) => ( @@ -174,23 +143,20 @@ const TemplateSelection: React.FC = memo(({ return (
- {/* Custom AI Templates */}

Custom

{customTemplateCards}
- {/* In Built Templates */}

In Built

-
+
{builtInTemplateCards}
); }); -TemplateSelection.displayName = 'TemplateSelection'; export default TemplateSelection; diff --git a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SortableSlide.tsx b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SortableSlide.tsx index 92068719..f189e66d 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SortableSlide.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SortableSlide.tsx @@ -73,14 +73,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick }: Sor
- {/*
-
-
- -
-
*/}
); } \ No newline at end of file diff --git a/electron/servers/nextjs/app/hooks/useCustomTemplates.ts b/electron/servers/nextjs/app/hooks/useCustomTemplates.ts index 641cf1f0..f619c730 100644 --- a/electron/servers/nextjs/app/hooks/useCustomTemplates.ts +++ b/electron/servers/nextjs/app/hooks/useCustomTemplates.ts @@ -403,7 +403,7 @@ export function useCustomTemplatePreview(presentationId: string) { setTotalLayouts(data.layouts.length); // Compile first 4 layouts for preview const compiled: CompiledLayout[] = []; - const layoutsToPreview = data.layouts.slice(0, 4); + const layoutsToPreview = data.layouts.slice(0, 2); for (const layout of layoutsToPreview) { try { diff --git a/electron/servers/nextjs/public/placeholder.jpg b/electron/servers/nextjs/public/placeholder.jpg new file mode 100644 index 00000000..9477065c Binary files /dev/null and b/electron/servers/nextjs/public/placeholder.jpg differ diff --git a/electron/servers/nextjs/public/placeholder.svg b/electron/servers/nextjs/public/placeholder.svg new file mode 100644 index 00000000..ca79c4c9 --- /dev/null +++ b/electron/servers/nextjs/public/placeholder.svg @@ -0,0 +1,7 @@ + + + + + + +