+
+
- {[...Array(3)].map((_, i) => (
+ {[...Array(15)].map((_, i) => (
))}
@@ -105,8 +115,7 @@ export const PresentationGrid = ({
key={presentation.id}
id={presentation.id}
title={presentation.title}
- created_at={presentation.created_at}
- slide={presentation.slides[0]}
+ presentation={presentation}
onDeleted={onPresentationDeleted}
/>
))}
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationListItem.tsx b/servers/nextjs/app/(dashboard)/dashboard/components/PresentationListItem.tsx
similarity index 100%
rename from servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationListItem.tsx
rename to servers/nextjs/app/(dashboard)/dashboard/components/PresentationListItem.tsx
diff --git a/servers/nextjs/app/(dashboard)/dashboard/loading.tsx b/servers/nextjs/app/(dashboard)/dashboard/loading.tsx
new file mode 100644
index 00000000..41cd811c
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/dashboard/loading.tsx
@@ -0,0 +1,25 @@
+import { Skeleton } from '@/components/ui/skeleton'
+import React from 'react'
+
+const loading = () => {
+ return (
+
+
+
+
+
+
+
+ {
+ Array.from({ length: 8 }).map((_, index) => (
+
+ ))
+ }
+
+
+
+
+ )
+}
+
+export default loading
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/page.tsx b/servers/nextjs/app/(dashboard)/dashboard/page.tsx
similarity index 100%
rename from servers/nextjs/app/(presentation-generator)/dashboard/page.tsx
rename to servers/nextjs/app/(dashboard)/dashboard/page.tsx
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/types.ts b/servers/nextjs/app/(dashboard)/dashboard/types.ts
similarity index 100%
rename from servers/nextjs/app/(presentation-generator)/dashboard/types.ts
rename to servers/nextjs/app/(dashboard)/dashboard/types.ts
diff --git a/servers/nextjs/app/(dashboard)/layout.tsx b/servers/nextjs/app/(dashboard)/layout.tsx
new file mode 100644
index 00000000..3a67a294
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/layout.tsx
@@ -0,0 +1,17 @@
+import React from 'react'
+import DashboardSidebar from './Components/DashboardSidebar'
+import DashboardNav from './Components/DashboardNav'
+
+const layout = ({ children }: { children: React.ReactNode }) => {
+ return (
+
+ )
+}
+
+export default layout
\ No newline at end of file
diff --git a/servers/nextjs/app/(dashboard)/templates/components/CreateCustomTemplate.tsx b/servers/nextjs/app/(dashboard)/templates/components/CreateCustomTemplate.tsx
new file mode 100644
index 00000000..9cb0cf39
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/templates/components/CreateCustomTemplate.tsx
@@ -0,0 +1,40 @@
+import { Plus, Sparkles } from 'lucide-react'
+import { useRouter } from 'next/navigation';
+import React from 'react'
+
+const CreateCustomTemplate = () => {
+ const router = useRouter();
+ return (
+
{
+ router.push('/custom-template')
+ }}
+ className='w-full rounded-xl border border-[#EDEEEF] cursor-pointer'>
+
+

+
+
+
+
+
+
+
+
+
Build Template
+
Build Your Own Template
+
+
+
+
+ )
+}
+
+export default CreateCustomTemplate
diff --git a/servers/nextjs/app/(dashboard)/templates/components/TemplatePanel.tsx b/servers/nextjs/app/(dashboard)/templates/components/TemplatePanel.tsx
new file mode 100644
index 00000000..270d0e10
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/templates/components/TemplatePanel.tsx
@@ -0,0 +1,247 @@
+"use client";
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+import { useRouter } from "next/navigation";
+import { Card } from "@/components/ui/card";
+import { ExternalLink, Loader2, Plus } from "lucide-react";
+import { templates } from "@/app/presentation-templates";
+import { TemplateWithData, TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils";
+import {
+ useCustomTemplateSummaries,
+ useCustomTemplatePreview,
+ CustomTemplates,
+} from "@/app/hooks/useCustomTemplates";
+import { CompiledLayout } from "@/app/hooks/compileLayout";
+import CreateCustomTemplate from "./CreateCustomTemplate";
+
+// 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 } = useCustomTemplatePreview(`${template.id}`);
+ const handleOpen = useCallback(() => {
+ if (template.id.startsWith('custom-')) {
+ router.push(`/template-preview/${template.id}`)
+ } else {
+ router.push(`/template-preview/custom-${template.id}`)
+ }
+ }
+ , [router, template.id]);
+
+ return (
+
+
+
+
+ {template.name}
+
+
+
+ {template.layoutCount}
+
+
+
+
+
+
+
+ {/* 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 (
+
+ );
+ })
+ ) : (
+ // Empty state placeholders
+ [...Array(Math.min(4, template.layoutCount))].map((_, index) => (
+
+ No preview
+
+ ))
+ )}
+
+
+
+
+
+ );
+}, (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
+ );
+});
+
+const InbuiltTemplateCard = React.memo(function InbuiltTemplateCard({
+ template,
+ onOpen,
+}: {
+ 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.name}
+
+
+
+ {template.layouts.length}
+
+
+
+
+
+
+ {template.description}
+
+
+
+ {previewLayouts.map((layout: TemplateWithData, index: number) => {
+ const LayoutComponent = layout.component;
+ return (
+
+ );
+ })}
+
+
+
+ );
+});
+
+const LayoutPreview = () => {
+ const [tab, setTab] = useState<'custom' | 'default'>('custom');
+ const router = useRouter();
+ const { templates: customTemplates, loading: customLoading } = useCustomTemplateSummaries();
+
+ useEffect(() => {
+ const existingScript = document.querySelector('script[src*="tailwindcss.com"]');
+ if (!existingScript) {
+ const script = document.createElement("script");
+ script.src = "https://cdn.tailwindcss.com";
+ script.async = true;
+ document.head.appendChild(script);
+ }
+ }, []);
+
+ const handleOpenPreview = useCallback((id: string) => router.push(`/template-preview/${id}`), [router]);
+
+
+
+
+ const inbuiltTemplateCards = useMemo(
+ () =>
+ templates.map((template: TemplateLayoutsWithSettings) => (
+
+ )),
+ [handleOpenPreview],
+ );
+
+ const customTemplateCards = useMemo(
+ () => customTemplates.map((template: CustomTemplates) =>
),
+ [customTemplates],
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+ {/* Inbuilt Templates Section */}
+ {tab === 'default' &&
+
+ {inbuiltTemplateCards}
+
+ }
+
+
+ {tab === 'custom' &&
+ {customLoading ? (
+
+
+ Loading custom templates...
+
+ ) : (
+
+
+ {customTemplateCards}
+
+ )}
+ }
+
+
+ );
+};
+
+export default LayoutPreview;
diff --git a/servers/nextjs/app/(dashboard)/templates/loading.tsx b/servers/nextjs/app/(dashboard)/templates/loading.tsx
new file mode 100644
index 00000000..a6a5d2ca
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/templates/loading.tsx
@@ -0,0 +1,58 @@
+import { Skeleton } from '@/components/ui/skeleton'
+import { Card } from '@/components/ui/card'
+
+const Loading = () => {
+ return (
+
+
+ {/* Header */}
+
+
+
+
+
+ {/* Inbuilt Templates Section */}
+
+
+
+ {Array.from({ length: 4 }).map((_, idx) => (
+
+
+
+
+
+
+ {Array.from({ length: 4 }).map((_, i) => (
+
+ ))}
+
+
+
+ ))}
+
+
+
+ {/* Custom Templates Section */}
+
+
+
+ )
+}
+
+export default Loading
+
diff --git a/servers/nextjs/app/(dashboard)/templates/page.tsx b/servers/nextjs/app/(dashboard)/templates/page.tsx
new file mode 100644
index 00000000..b4bf6695
--- /dev/null
+++ b/servers/nextjs/app/(dashboard)/templates/page.tsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import TemplatePanel from './components/TemplatePanel'
+
+const page = () => {
+ return (
+
+ )
+}
+
+export default page
diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx
index 60a43e59..07977556 100644
--- a/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx
+++ b/servers/nextjs/app/(presentation-generator)/custom-template/components/APIKeyWarning.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import Header from "@/app/(presentation-generator)/dashboard/components/Header";
+import Header from "@/app/(dashboard)/dashboard/components/Header";
export const APIKeyWarning: React.FC = () => {
return (
@@ -8,7 +8,7 @@ export const APIKeyWarning: React.FC = () => {
- Please add "GOOGLE_API_KEY" to enable template creation via AI.
+ Please add "GOOGLE_API_KEY" to enable template creation via AI.
Please add your OpenAI API Key to process the layout
diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/components/LoadingSpinner.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/components/LoadingSpinner.tsx
index e379f73f..6241110e 100644
--- a/servers/nextjs/app/(presentation-generator)/custom-template/components/LoadingSpinner.tsx
+++ b/servers/nextjs/app/(presentation-generator)/custom-template/components/LoadingSpinner.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { Loader2 } from "lucide-react";
-import Header from "@/app/(presentation-generator)/dashboard/components/Header";
+import Header from "@/app/(dashboard)/dashboard/components/Header";
interface LoadingSpinnerProps {
message: string;
diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
index cd355765..dc8ee5e0 100644
--- a/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
+++ b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx
@@ -2,7 +2,7 @@
import React, { useEffect } from "react";
import FontManager from "./components/FontManager";
-import Header from "../dashboard/components/Header";
+import Header from "../../(dashboard)/dashboard/components/Header";
import { useCustomLayout } from "./hooks/useCustomLayout";
import { useFontManagement } from "./hooks/useFontManagement";
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationCard.tsx b/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationCard.tsx
deleted file mode 100644
index 588f61dd..00000000
--- a/servers/nextjs/app/(presentation-generator)/dashboard/components/PresentationCard.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-import React, { useMemo } from "react";
-
-import { Card } from "@/components/ui/card";
-import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard";
-import { DotsVerticalIcon, TrashIcon } from "@radix-ui/react-icons";
-import {
- Popover,
- PopoverTrigger,
- PopoverContent,
-} from "@/components/ui/popover";
-import { useRouter } from "next/navigation";
-import { toast } from "sonner";
-import SlideScale from "../../components/PresentationRender";
-
-export const PresentationCard = ({
- id,
- title,
- created_at,
- slide,
- onDeleted
-}: {
- id: string;
- title: string;
- created_at: string;
- slide: any;
- onDeleted?: (presentationId: string) => void;
-}) => {
- const router = useRouter();
-
-
-
-
- const handlePreview = (e: React.MouseEvent) => {
- e.preventDefault();
- router.push(`/presentation?id=${id}`);
- };
-
- const handleDelete = async (e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
-
-
- const response = await DashboardApi.deletePresentation(id);
-
- if (response) {
- toast.success("Presentation deleted", {
- description: "The presentation has been deleted successfully",
- });
- if (onDeleted) {
- onDeleted(id);
- }
- } else {
- toast.error("Error deleting presentation");
- }
- };
- return (
-
-
- {/* Date */}
-
-
- {new Date(created_at).toLocaleDateString()}
-
-
- e.stopPropagation()}>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* Icon and Title */}
-
-
-
- );
-};
diff --git a/servers/nextjs/app/(presentation-generator)/dashboard/loading.tsx b/servers/nextjs/app/(presentation-generator)/dashboard/loading.tsx
deleted file mode 100644
index d3350f27..00000000
--- a/servers/nextjs/app/(presentation-generator)/dashboard/loading.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { Skeleton } from '@/components/ui/skeleton'
-import React from 'react'
-import Header from './components/Header'
-import Wrapper from '@/components/Wrapper'
-
-const loading = () => {
- return (
-
-
-
-
-
-
- Slide Presentation
-
-
- {
- Array.from({ length: 8 }).map((_, index) => (
-
- ))
- }
-
-
-
-
- )
-}
-
-export default loading
\ No newline at end of file
diff --git a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx
index 06d44619..8abd3492 100644
--- a/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx
+++ b/servers/nextjs/app/(presentation-generator)/documents-preview/components/DocumentPreviewPage.tsx
@@ -27,7 +27,7 @@ import MarkdownRenderer from "./MarkdownRenderer";
import { getIconFromFile } from "../../utils/others";
import { ChevronRight, PanelRightOpen, X } from "lucide-react";
import ToolTip from "@/components/ToolTip";
-import Header from "@/app/(presentation-generator)/dashboard/components/Header";
+import Header from "@/app/(dashboard)/dashboard/components/Header";
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
// Types
@@ -147,7 +147,7 @@ const DocumentsPreviewPage: React.FC = () => {
(fileItem: FileItem) => fileItem.file_path
);
trackEvent(MixpanelEvent.DocumentsPreview_Create_Presentation_API_Call);
- const createResponse = await PresentationGenerationApi.createPresentation(
+ const createResponse = await PresentationGenerationApi.createPresentation(
{
content: config?.prompt ?? "",
n_slides: config?.slides ? parseInt(config.slides) : null,
@@ -240,9 +240,8 @@ const DocumentsPreviewPage: React.FC = () => {
updateSelectedDocument(key)}
- className={`${
- selectedDocument === key ? "border border-blue-500" : ""
- } flex p-2 rounded-sm gap-2 items-center cursor-pointer`}
+ className={`${selectedDocument === key ? "border border-blue-500" : ""
+ } flex p-2 rounded-sm gap-2 items-center cursor-pointer`}
>
![]()
{