feat: Implement dashboard design & templates page
This commit is contained in:
parent
acb850b50c
commit
cb8ad52f0c
31 changed files with 6092 additions and 207 deletions
64
servers/nextjs/app/(dashboard)/Components/DashboardNav.tsx
Normal file
64
servers/nextjs/app/(dashboard)/Components/DashboardNav.tsx
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"use client";
|
||||
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import React, { } from 'react'
|
||||
import { defaultNavItems } from './DashboardSidebar';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
const DashboardNav = () => {
|
||||
const pathname = usePathname();
|
||||
const activeTab = pathname.split("?")[0].split("/").pop();
|
||||
const activeItem = defaultNavItems.find((i: any) => i.key === activeTab);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 right-0 z-50 py-[28px] backdrop-blur ">
|
||||
<div className="flex xl:flex-row flex-col gap-6 xl:gap-0 items-center justify-between">
|
||||
<h3 className=" text-[28px] tracking-[-0.84px] font-unbounded font-normal text-[#101828] flex items-center gap-2">
|
||||
|
||||
{activeItem?.label ?? (activeTab && activeTab?.charAt(0).toUpperCase() + activeTab?.slice(1))}
|
||||
</h3>
|
||||
<div className="flex gap-2.5 max-sm:w-full max-md:justify-center max-sm:flex-wrap">
|
||||
|
||||
|
||||
|
||||
{activeTab !== "playground" && activeTab !== "theme" && <Link
|
||||
href="/generate"
|
||||
className="inline-flex items-center gap-2 rounded-xl px-4 py-2.5 text-black text-sm font-medium shadow-sm hover:shadow-md"
|
||||
aria-label="Create new presentation"
|
||||
style={{
|
||||
borderRadius: "48px",
|
||||
background: "linear-gradient(270deg, #D5CAFC 2.4%, #E3D2EB 27.88%, #F4DCD3 69.23%, #FDE4C2 100%)",
|
||||
}}
|
||||
>
|
||||
|
||||
<span className="hidden md:inline">New presentation</span>
|
||||
<span className="md:hidden">New</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Link>}
|
||||
{activeTab === "theme" &&
|
||||
<Link
|
||||
href="/theme?tab=new-theme"
|
||||
className="inline-flex items-center font-inter font-normal gap-2 rounded-xl px-4 py-2.5 text-black text-sm shadow-sm hover:shadow-md"
|
||||
aria-label="Create new themes"
|
||||
style={{
|
||||
borderRadius: "48px",
|
||||
background: "linear-gradient(270deg, #D5CAFC 2.4%, #E3D2EB 27.88%, #F4DCD3 69.23%, #FDE4C2 100%)",
|
||||
}}
|
||||
>
|
||||
<span className="hidden md:inline">New Themes</span>
|
||||
<span className="md:hidden">New</span>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Link>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DashboardNav
|
||||
162
servers/nextjs/app/(dashboard)/Components/DashboardSidebar.tsx
Normal file
162
servers/nextjs/app/(dashboard)/Components/DashboardSidebar.tsx
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { LayoutDashboard, Star, Brain, Settings } from "lucide-react";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
|
||||
|
||||
export const defaultNavItems = [
|
||||
{ key: "dashboard" as const, label: "Dashboard", icon: LayoutDashboard },
|
||||
{ key: "templates" as const, label: "Standard", icon: Star },
|
||||
{ key: "designs" as const, label: "Smart", icon: Brain },
|
||||
|
||||
|
||||
|
||||
];
|
||||
export const BelongingNavItems = [
|
||||
{ key: "settings" as const, label: "Settings", icon: Settings },
|
||||
]
|
||||
|
||||
const DashboardSidebar = () => {
|
||||
|
||||
|
||||
const pathname = usePathname();
|
||||
const activeTab = pathname.split("?")[0].split("/").pop();
|
||||
const router = useRouter();
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const [profileMenuOpen, setProfileMenuOpen] = React.useState(false);
|
||||
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
|
||||
const handleMenuNavigate = (href: string) => {
|
||||
setProfileMenuOpen(false);
|
||||
router.push(href);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<aside
|
||||
className="sticky top-0 h-screen w-[115px] flex flex-col justify-between bg-[#F6F6F9] backdrop-blur border-r border-slate-200/60 px-4 py-8"
|
||||
aria-label="Dashboard sidebar"
|
||||
>
|
||||
<div>
|
||||
|
||||
<div onClick={() => router.push("/dashboard")} className="flex items-center pb-6 border-b border-slate-200/60 gap-2 ">
|
||||
<div className="bg-[#7C51F8] rounded-full cursor-pointer p-1 flex justify-center items-center mx-auto">
|
||||
<img src="/logo-with-bg.png" alt="Presenton logo" className="h-[40px] object-contain w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
{/* <div className="mt-3">
|
||||
{mounted && (auth?.user || auth?.userEmail) ? (
|
||||
<Link
|
||||
prefetch={false}
|
||||
href="/profile"
|
||||
className="w-full flex gap-3 items-center cursor-pointer rounded-2xl ring-1 ring-inset ring-slate-200 bg-white/80 hover:bg-white transition-colors px-3 py-2"
|
||||
aria-label="Open profile"
|
||||
title="Profile"
|
||||
>
|
||||
<div className="h-8 w-8 rounded-full bg-[#5146E5]/10 flex items-center justify-center text-[#5146E5] text-xs font-semibold">
|
||||
{(auth?.user?.name?.[0] || auth?.userEmail?.[0] || "?").toUpperCase()}
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
<div className="text-xs font-semibold text-slate-900 truncate">{auth?.user?.name || auth?.userEmail}</div>
|
||||
{auth?.userEmail && <div className="text-[10px] text-slate-500 truncate">{auth.userEmail}</div>}
|
||||
</div>
|
||||
</Link>
|
||||
) : (
|
||||
<div
|
||||
className="w-full flex items-center cursor-pointer rounded-2xl ring-1 ring-inset ring-slate-200 bg-white/80 px-3 py-2 gap-3"
|
||||
>
|
||||
<UserRoundCog className="h-4 w-4 text-slate-700" />
|
||||
<div className="flex-1">
|
||||
<div className="bg-slate-100 animate-pulse rounded w-full h-4 mb-1"></div>
|
||||
<div className="bg-slate-100 animate-pulse rounded w-2/3 h-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div> */}
|
||||
|
||||
<nav className="pt-6" aria-label="Dashboard sections">
|
||||
<div className=" space-y-6">
|
||||
|
||||
{/* Dashboard */}
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/dashboard`}
|
||||
className={[
|
||||
"flex flex-col tex-center items-center gap-2 transition-colors",
|
||||
pathname === "/dashboard" ? "" : "ring-transparent",
|
||||
].join(" ")}
|
||||
aria-label="Dashboard"
|
||||
title="Dashboard"
|
||||
>
|
||||
<LayoutDashboard className={["h-4 w-4", pathname === "/dashboard" ? "text-[#5146E5]" : "text-slate-600"].join(" ")} />
|
||||
<span className="text-[11px] text-slate-800">Dashboard</span>
|
||||
</Link>
|
||||
<Link
|
||||
prefetch={false}
|
||||
href={`/templates`}
|
||||
className={[
|
||||
"flex flex-col tex-center items-center gap-2 transition-colors",
|
||||
pathname === "/templates" ? "" : "ring-transparent",
|
||||
].join(" ")}
|
||||
aria-label="Templates"
|
||||
title="Templates"
|
||||
>
|
||||
<div className="flex flex-col cursor-pointer tex-center items-center gap-2 transition-colors">
|
||||
<Star className={`h-4 w-4 ${pathname === "/templates" ? "text-[#5146E5]" : "text-slate-600"}`} />
|
||||
<span className="text-[11px] text-slate-800">Templates</span>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className=" pt-5 border-t border-slate-200/60 "
|
||||
>
|
||||
{BelongingNavItems.map(({ key, label: itemLabel, icon: Icon }) => {
|
||||
const isActive = activeTab === key;
|
||||
return (
|
||||
<Link
|
||||
prefetch={false}
|
||||
key={key}
|
||||
href={`/${key}`}
|
||||
className={[
|
||||
"flex flex-col tex-center items-center gap-2 transition-colors ",
|
||||
isActive ? "" : "ring-transparent",
|
||||
].join(" ")}
|
||||
aria-label={itemLabel}
|
||||
title={itemLabel}
|
||||
>
|
||||
<Icon className={["h-4 w-4", isActive ? "text-[#5146E5]" : "text-slate-600"].join(" ")} />
|
||||
<span className="text-[11px] text-slate-800">{itemLabel}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardSidebar;
|
||||
|
||||
|
||||
|
|
@ -2,12 +2,10 @@
|
|||
|
||||
import React, { useState, useEffect } from "react";
|
||||
|
||||
import Wrapper from "@/components/Wrapper";
|
||||
import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard";
|
||||
import { PresentationGrid } from "@/app/(presentation-generator)/dashboard/components/PresentationGrid";
|
||||
import { PresentationGrid } from "@/app/(dashboard)/dashboard/components/PresentationGrid";
|
||||
|
||||
|
||||
import Header from "@/app/(presentation-generator)/dashboard/components/Header";
|
||||
|
||||
const DashboardPage: React.FC = () => {
|
||||
const [presentations, setPresentations] = useState<any>(null);
|
||||
|
|
@ -46,24 +44,14 @@ const DashboardPage: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#E9E8F8]">
|
||||
<Header />
|
||||
<Wrapper>
|
||||
<main className="container mx-auto px-4 py-8">
|
||||
<section>
|
||||
<h2 className="text-2xl font-roboto font-medium mb-6">
|
||||
Slide Presentation
|
||||
</h2>
|
||||
<PresentationGrid
|
||||
presentations={presentations}
|
||||
type="slide"
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
onPresentationDeleted={removePresentation}
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
</Wrapper>
|
||||
<div className="min-h-screen w-full">
|
||||
<PresentationGrid
|
||||
presentations={presentations}
|
||||
type="slide"
|
||||
isLoading={isLoading}
|
||||
error={error}
|
||||
onPresentationDeleted={removePresentation}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
'use client'
|
||||
import React from "react";
|
||||
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { DashboardApi } from "@/app/(presentation-generator)/services/api/dashboard";
|
||||
import { EllipsisVertical, Star, Trash } from "lucide-react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import { useFontLoader } from "@/app/(presentation-generator)/hooks/useFontLoader";
|
||||
import SlideScale from "@/app/(presentation-generator)/components/PresentationRender";
|
||||
import MarkdownRenderer from "@/app/(presentation-generator)/documents-preview/components/MarkdownRenderer";
|
||||
|
||||
export const PresentationCard = ({
|
||||
id,
|
||||
title,
|
||||
presentation,
|
||||
onDeleted
|
||||
}: {
|
||||
id: string;
|
||||
title: string;
|
||||
presentation: any;
|
||||
onDeleted?: (presentationId: string) => void;
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
// useFontLoader(presentation.fonts || []);
|
||||
const handlePreview = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
router.push(`/presentation?id=${id}&type=${presentation.type}`);
|
||||
};
|
||||
|
||||
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");
|
||||
}
|
||||
};
|
||||
const firstSlide = presentation?.slides?.[0];
|
||||
return (
|
||||
<Card
|
||||
suppressHydrationWarning={true}
|
||||
onClick={handlePreview}
|
||||
className="bg-[#F8FBFB] shadow-none sm:shadow-none presentation-card rounded-[12px] p-0 group hover:shadow-md transition-all duration-500 slide-theme cursor-pointer overflow-hidden flex flex-col"
|
||||
>
|
||||
<div suppressHydrationWarning={true} className="flex flex-col flex-1 relative z-40">
|
||||
{/* <p className=" text-xs font-syne absolute top-2 flex gap-1 capitalize items-center left-2 rounded-[100px] px-2.5 py-1 bg-[#3A3A3AF5] text-white font-semibold z-40 ">
|
||||
|
||||
{presentation.type}
|
||||
</p> */}
|
||||
|
||||
<img src="/card_bg.svg" alt="" className="absolute top-0 left-0 w-full h-full object-cover" />
|
||||
<div className="scale-[0.75] mt-4 border border-gray-300 rounded-lg overflow-hidden">
|
||||
|
||||
<SlideScale slide={firstSlide} />
|
||||
</div>
|
||||
|
||||
<div className="w-full py-2.5 px-5 mt-auto z-40 relative bg-white border-t border-[#EDEEEF]">
|
||||
<div className="flex items-center justify-between gap-7 w-full">
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<div className="text-sm text-[#191919] font-semibold overflow-hidden line-clamp-2">
|
||||
{<MarkdownRenderer content={title} />}
|
||||
</div>
|
||||
<p className="text-[#808080] text-sm font-syne">
|
||||
{new Date(presentation?.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<Popover>
|
||||
<PopoverTrigger className="w-6 h-6 hover:bg-gray-100 rounded-full flex items-center justify-center text-gray-500 hover:text-gray-700" onClick={(e) => e.stopPropagation()}>
|
||||
<EllipsisVertical className="w-6 h-6 text-gray-500" />
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="bg-white w-[200px]">
|
||||
<button
|
||||
className="flex items-center justify-between w-full px-2 py-1 hover:bg-gray-100"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<p>Delete</p>
|
||||
<Trash className="w- h-4 text-red-500" />
|
||||
</button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
@ -3,6 +3,7 @@ import { PresentationCard } from "./PresentationCard";
|
|||
import { PlusIcon } from "@radix-ui/react-icons";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { PresentationResponse } from "@/app/(presentation-generator)/services/api/dashboard";
|
||||
import { ArrowRight } from "lucide-react";
|
||||
|
||||
interface PresentationGridProps {
|
||||
presentations: PresentationResponse[];
|
||||
|
|
@ -41,35 +42,44 @@ export const PresentationGrid = ({
|
|||
const CreateNewCard = () => (
|
||||
<div
|
||||
onClick={handleCreateNewPresentation}
|
||||
className="flex flex-col gap-4 min-h-[200px] cursor-pointer group border border-gray-400 hover:border-primary/60 bg-white/70 hover:bg-white/80 rounded-lg items-center justify-center transition-all duration-300"
|
||||
className="flex flex-col cursor-pointer group ring-1 ring-inset ring-slate-200 hover:ring-[#8A7DFF]/40 bg-white/80 rounded-xl overflow-hidden transition-all duration-300"
|
||||
>
|
||||
<div className="rounded-full bg-gray-200 group-hover:bg-primary/10 p-4 transition-all duration-300">
|
||||
<PlusIcon className="w-8 h-8 text-gray-500 group-hover:text-primary transition-all duration-300" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<h3 className="font-semibold text-gray-700 group-hover:text-gray-900 mb-1">
|
||||
Create {type === "slide" ? "New" : "Video"} Presentation
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500 group-hover:text-gray-600 px-4">
|
||||
Start from scratch and bring your ideas to life
|
||||
</p>
|
||||
<img src="/create_presentation.png" alt="New Presentation" className="w-full aspect-[16/11] object-cover" />
|
||||
<div className="flex items-center gap-3 p-3 mt-auto border border-[#EDEEEF]">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="45" height="46" viewBox="0 0 45 46" fill="none" className="flex-shrink-0">
|
||||
<rect width="45" height="46" rx="8" fill="#FB6514" />
|
||||
<g clipPath="url(#clip0_1789_6104)">
|
||||
<path d="M16.0332 17.1807L28.9665 17.1807" stroke="white" strokeWidth="1.11" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M28.3197 17.1807L28.3197 24.294C28.3197 24.637 28.1834 24.966 27.9409 25.2085C27.6983 25.4511 27.3694 25.5873 27.0264 25.5873L17.973 25.5873C17.63 25.5873 17.301 25.4511 17.0585 25.2085C16.8159 24.966 16.6797 24.637 16.6797 24.294L16.6797 17.1807" stroke="white" strokeWidth="1.11" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M19.2676 28.8202L22.5009 25.5869L25.7342 28.8202" stroke="white" strokeWidth="1.11" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1789_6104">
|
||||
<rect width="15.52" height="15.52" fill="white" transform="translate(14.7402 15.2402)" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
<div>
|
||||
<h4 className="text-sm text-[#191919] font-semibold tracking-[0.14px]">Create New Presentation</h4>
|
||||
<p className="text-sm text-[#808080] font-medium tracking-[0.14px] flex items-center gap-2">Get Started <ArrowRight className="w-4 h-4" /></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="flex flex-col gap-4 min-h-[200px] cursor-pointer group border border-gray-400 bg-white/70 rounded-lg items-center justify-center animate-pulse">
|
||||
<div className="rounded-full bg-gray-200 p-4">
|
||||
<div className="grid grid-cols-1 mt-10 md:grid-cols-2 lg:grid-cols-4 gap-5 sm:gap-6 w-full">
|
||||
<div className="flex flex-col gap-4 min-h-[200px] cursor-pointer group ring-1 ring-inset ring-slate-200 bg-white/80 rounded-xl items-center justify-center animate-pulse">
|
||||
<div className="rounded-full bg-slate-200 p-4">
|
||||
<div className="w-8 h-8" />
|
||||
</div>
|
||||
<div className="text-center space-y-2">
|
||||
<div className="h-4 bg-gray-200 rounded w-32 mx-auto"></div>
|
||||
<div className="h-3 bg-gray-200 rounded w-48 mx-auto"></div>
|
||||
<div className="h-4 bg-slate-200 rounded w-32 mx-auto"></div>
|
||||
<div className="h-3 bg-slate-200 rounded w-48 mx-auto"></div>
|
||||
</div>
|
||||
</div>
|
||||
{[...Array(3)].map((_, i) => (
|
||||
{[...Array(15)].map((_, i) => (
|
||||
<ShimmerCard key={i} />
|
||||
))}
|
||||
</div>
|
||||
|
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
25
servers/nextjs/app/(dashboard)/dashboard/loading.tsx
Normal file
25
servers/nextjs/app/(dashboard)/dashboard/loading.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import React from 'react'
|
||||
|
||||
const loading = () => {
|
||||
return (
|
||||
<div className=''>
|
||||
|
||||
|
||||
<div className='container mx-auto px-4 py-8'>
|
||||
|
||||
|
||||
<div className=" mx-auto pb-10 grid xl:grid-cols-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 ">
|
||||
{
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-72 w-full bg-gray-300 aspect-video mx-auto" />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default loading
|
||||
17
servers/nextjs/app/(dashboard)/layout.tsx
Normal file
17
servers/nextjs/app/(dashboard)/layout.tsx
Normal file
|
|
@ -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 (
|
||||
<div className='flex gap-6 pr-4 bg-white'>
|
||||
<DashboardSidebar />
|
||||
<div className='w-full'>
|
||||
<DashboardNav />
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default layout
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
onClick={() => {
|
||||
router.push('/custom-template')
|
||||
}}
|
||||
className='w-full rounded-xl border border-[#EDEEEF] cursor-pointer'>
|
||||
<div className='relative h-[250px] flex justify-center items-center '>
|
||||
<img src="/card_bg.svg" alt="" className="absolute top-0 z-[1] left-0 w-full h-full object-cover" />
|
||||
<div className='w-[36px] h-[36px] relative z-[4] rounded-full bg-[#7A5AF8] flex items-center justify-center'
|
||||
style={{
|
||||
background: 'linear-gradient(0deg, rgba(0, 0, 0, 0.20) 0%, rgba(0, 0, 0, 0.20) 100%), #FFF'
|
||||
}}
|
||||
><div className='w-[26px] h-[26px] rounded-full bg-white flex items-center justify-center'>
|
||||
|
||||
<Plus className='w-4 h-4 text-[#A2A0A1]' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='px-5 py-4 bg-white flex items-center gap-4 border-t border-[#EDEEEF]'>
|
||||
<div className='bg-[#7A5AF8] w-[45px] h-[45px] rounded-lg p-2 flex items-center justify-center'>
|
||||
|
||||
<Sparkles className='w-6 h-6 text-white' />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className='text-[#191919] text-sm font-semibold '>Build Template</h4>
|
||||
<p className='flex text-[#808080] text-sm font-medium items-center gap-2'>Build Your Own Template</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateCustomTemplate
|
||||
|
|
@ -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 (
|
||||
<Card
|
||||
className="cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-lg font-bold text-gray-900">
|
||||
{template.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-2.5 py-0.5 bg-purple-100 text-purple-800 rounded-full text-sm font-medium">
|
||||
{template.layoutCount}
|
||||
</span>
|
||||
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-purple-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{/* Layout previews */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{loading ? (
|
||||
// Loading placeholders
|
||||
[...Array(Math.min(4, template.layoutCount))].map((_, index) => (
|
||||
<div
|
||||
key={`${template.id}-loading-${index}`}
|
||||
className="relative bg-gradient-to-br from-purple-50 to-blue-50 border border-gray-200 overflow-hidden aspect-video rounded flex items-center justify-center"
|
||||
>
|
||||
<Loader2 className="w-4 h-4 text-purple-300 animate-spin" />
|
||||
</div>
|
||||
))
|
||||
) : previewLayouts.length > 0 ? (
|
||||
// Actual layout previews
|
||||
previewLayouts.slice(0, 4).map((layout: CompiledLayout, index: number) => {
|
||||
const LayoutComponent = layout.component;
|
||||
return (
|
||||
<div
|
||||
key={`${template.id}-preview-${index}`}
|
||||
className="relative bg-gray-100 border border-gray-200 overflow-hidden aspect-video rounded"
|
||||
>
|
||||
<div className="absolute inset-0 bg-transparent z-10" />
|
||||
<div
|
||||
className="transform scale-[0.12] origin-top-left"
|
||||
style={{ width: "833.33%", height: "833.33%" }}
|
||||
>
|
||||
<LayoutComponent data={layout.sampleData} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
// Empty state placeholders
|
||||
[...Array(Math.min(4, template.layoutCount))].map((_, index) => (
|
||||
<div
|
||||
key={`${template.id}-empty-${index}`}
|
||||
className="relative bg-gray-100 border border-gray-200 overflow-hidden aspect-video rounded flex items-center justify-center"
|
||||
>
|
||||
<span className="text-xs text-gray-400">No preview</span>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}, (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 (
|
||||
<Card
|
||||
key={template.id}
|
||||
className="cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden"
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<div className="p-5">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 className="text-xl font-bold text-gray-900 capitalize">
|
||||
{template.name}
|
||||
</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="px-2.5 py-0.5 bg-blue-100 text-blue-800 rounded-full text-sm font-medium">
|
||||
{template.layouts.length}
|
||||
</span>
|
||||
<ExternalLink className="w-4 h-4 text-gray-400 group-hover:text-blue-600 transition-colors" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-gray-600 mb-4 line-clamp-2">
|
||||
{template.description}
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{previewLayouts.map((layout: TemplateWithData, index: number) => {
|
||||
const LayoutComponent = layout.component;
|
||||
return (
|
||||
<div
|
||||
key={`${template.id}-preview-${index}`}
|
||||
className="relative bg-gray-100 border border-gray-200 overflow-hidden aspect-video rounded"
|
||||
>
|
||||
<div className="absolute inset-0 bg-transparent z-10" />
|
||||
<div
|
||||
className="transform scale-[0.12] origin-top-left"
|
||||
style={{ width: "833.33%", height: "833.33%" }}
|
||||
>
|
||||
<LayoutComponent data={layout.sampleData} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
});
|
||||
|
||||
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) => (
|
||||
<InbuiltTemplateCard key={template.id} template={template} onOpen={handleOpenPreview} />
|
||||
)),
|
||||
[handleOpenPreview],
|
||||
);
|
||||
|
||||
const customTemplateCards = useMemo(
|
||||
() => customTemplates.map((template: CustomTemplates) => <CustomTemplateCard key={template.id} template={template} />),
|
||||
[customTemplates],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen ">
|
||||
|
||||
<div className="l mx-auto px-6 py-8">
|
||||
<div className='p-1 rounded-[40px] bg-[#ffffff] w-fit border border-[#EDEEEF] flex items-center justify-center '>
|
||||
<button className='px-5 py-2 text-xs font-medium text-[#3A3A3A] rounded-[70px]'
|
||||
onClick={() => setTab('custom')}
|
||||
style={{
|
||||
background: tab === 'custom' ? '#F4F3FF' : 'transparent',
|
||||
color: tab === 'custom' ? '#5146E5' : '#3A3A3A'
|
||||
}}
|
||||
>Custom</button>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className='mx-1' width="2" height="17" viewBox="0 0 2 17" fill="none">
|
||||
<path d="M1 0V16.5" stroke="#EDECEC" strokeWidth="2" />
|
||||
</svg>
|
||||
<button className='px-5 py-2 text-xs font-medium text-[#3A3A3A] rounded-[70px]'
|
||||
onClick={() => setTab('default')}
|
||||
style={{
|
||||
background: tab === 'default' ? '#F4F3FF' : 'transparent',
|
||||
color: tab === 'default' ? '#5146E5' : '#3A3A3A'
|
||||
}}
|
||||
>Built-in</button>
|
||||
</div>
|
||||
|
||||
{/* Inbuilt Templates Section */}
|
||||
{tab === 'default' && <section className="my-12">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{inbuiltTemplateCards}
|
||||
</div>
|
||||
</section>}
|
||||
|
||||
|
||||
{tab === 'custom' && <section className="my-12">
|
||||
{customLoading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-blue-600" />
|
||||
<span className="ml-3 text-gray-600">Loading custom templates...</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<CreateCustomTemplate />
|
||||
{customTemplateCards}
|
||||
</div>
|
||||
)}
|
||||
</section>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default LayoutPreview;
|
||||
58
servers/nextjs/app/(dashboard)/templates/loading.tsx
Normal file
58
servers/nextjs/app/(dashboard)/templates/loading.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { Card } from '@/components/ui/card'
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<div className="max-w-7xl mx-auto px-6 py-8">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-8">
|
||||
<Skeleton className="h-9 w-48 mx-auto mb-2" />
|
||||
<Skeleton className="h-5 w-64 mx-auto" />
|
||||
</div>
|
||||
|
||||
{/* Inbuilt Templates Section */}
|
||||
<section className="mb-12">
|
||||
<Skeleton className="h-6 w-40 mb-6" />
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
{Array.from({ length: 4 }).map((_, idx) => (
|
||||
<Card key={idx} className="overflow-hidden">
|
||||
<div className="p-5">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<Skeleton className="h-6 w-28" />
|
||||
<div className="flex items-center gap-2">
|
||||
<Skeleton className="h-6 w-8 rounded-full" />
|
||||
<Skeleton className="h-4 w-4" />
|
||||
</div>
|
||||
</div>
|
||||
<Skeleton className="h-4 w-full mb-1" />
|
||||
<Skeleton className="h-4 w-3/4 mb-4" />
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Skeleton key={i} className="aspect-video rounded" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Custom Templates Section */}
|
||||
<section>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<Skeleton className="h-6 w-48" />
|
||||
<Skeleton className="h-10 w-44 rounded-md" />
|
||||
</div>
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Skeleton className="h-8 w-8 rounded-full" />
|
||||
<Skeleton className="h-5 w-48 ml-3" />
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Loading
|
||||
|
||||
10
servers/nextjs/app/(dashboard)/templates/page.tsx
Normal file
10
servers/nextjs/app/(dashboard)/templates/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React from 'react'
|
||||
import TemplatePanel from './components/TemplatePanel'
|
||||
|
||||
const page = () => {
|
||||
return (
|
||||
<TemplatePanel />
|
||||
)
|
||||
}
|
||||
|
||||
export default page
|
||||
|
|
@ -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 = () => {
|
|||
<div className="flex items-center justify-center aspect-video mx-auto px-6">
|
||||
<div className="text-center space-y-2 my-6 bg-white p-10 rounded-lg shadow-lg">
|
||||
<h1 className="text-xl font-bold text-gray-900">
|
||||
Please add "GOOGLE_API_KEY" to enable template creation via AI.
|
||||
Please add "GOOGLE_API_KEY" to enable template creation via AI.
|
||||
</h1>
|
||||
<h1 className="text-xl font-bold text-gray-900">Please add your OpenAI API Key to process the layout</h1>
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto">
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<Card
|
||||
onClick={handlePreview}
|
||||
|
||||
className="bg-white rounded-[8px] slide-theme cursor-pointer overflow-hidden p-4"
|
||||
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{/* Date */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-[#667085] text-sm font-roboto pt-2">
|
||||
{new Date(created_at).toLocaleDateString()}
|
||||
</p>
|
||||
<Popover>
|
||||
<PopoverTrigger className="w-6 h-6 rounded-full flex items-center justify-center text-gray-500 hover:text-gray-700" onClick={(e) => e.stopPropagation()}>
|
||||
|
||||
|
||||
<DotsVerticalIcon className="w-4 h-4 text-gray-500" />
|
||||
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" className="bg-white w-[200px]">
|
||||
<button
|
||||
className="flex items-center justify-between w-full px-2 py-1 hover:bg-gray-100"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<p>Delete</p>
|
||||
<TrashIcon className="w-4 h-4 text-red-500" />
|
||||
</button>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<div className=" slide-box relative overflow-hidden border aspect-video"
|
||||
style={{
|
||||
|
||||
}}
|
||||
>
|
||||
<div className="absolute bg-transparent z-40 top-0 left-0 w-full h-full" />
|
||||
<div className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]">
|
||||
<SlideScale slide={slide} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Icon and Title */}
|
||||
<div className="flex items-center gap-2 pb-1">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="w-full h-full max-w-[20px] max-h-[20px]"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
>
|
||||
<path
|
||||
d="M15.75 0.75V6C15.75 6.42 16.08 6.75 16.5 6.75H21.75M9.75 17.25H7.5C7.08 17.25 6.75 16.92 6.75 16.5V12C6.75 11.58 7.08 11.25 7.5 11.25H13.5C13.92 11.25 14.25 11.58 14.25 12V14.25M21.75 6.3V22.5C21.75 22.92 21.42 23.25 21 23.25H3C2.58 23.25 2.25 22.92 2.25 22.5V1.5C2.25 1.08 2.58 0.75 3 0.75H16.275C16.47 0.75 16.665 0.825 16.815 0.975L21.54 5.775C21.675 5.925 21.75 6.105 21.75 6.3ZM10.5 14.25H16.5C16.92 14.25 17.25 14.58 17.25 15V19.5C17.25 19.92 16.92 20.25 16.5 20.25H10.5C10.08 20.25 9.75 19.92 9.75 19.5V15C9.75 14.58 10.08 14.25 10.5 14.25Z"
|
||||
stroke="black"
|
||||
strokeWidth="1.5"
|
||||
/>
|
||||
</svg>
|
||||
<p className="text-[#667085] text-sm ml-1 line-clamp-2 font-roboto">
|
||||
{title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div className=''>
|
||||
<Header />
|
||||
<Wrapper className=''>
|
||||
<div className='container mx-auto px-4 py-8'>
|
||||
|
||||
<h2 className="text-2xl font-roboto font-medium my-6">
|
||||
Slide Presentation
|
||||
</h2>
|
||||
<div className=" mx-auto pb-10 grid xl:grid-cols-4 grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 ">
|
||||
{
|
||||
Array.from({ length: 8 }).map((_, index) => (
|
||||
<Skeleton key={index} className="h-72 w-full bg-gray-300 aspect-video mx-auto" />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default loading
|
||||
|
|
@ -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 = () => {
|
|||
<div
|
||||
key={key}
|
||||
onClick={() => 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`}
|
||||
>
|
||||
<img
|
||||
className="h-6 w-6 border border-gray-200"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
import { Metadata } from 'next'
|
||||
import OutlinePage from './components/OutlinePage'
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
} from "@/utils/providerUtils";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import LLMProviderSelection from "@/components/LLMSelection";
|
||||
import Header from "../dashboard/components/Header";
|
||||
import Header from "../../(dashboard)/dashboard/components/Header";
|
||||
import { LLMConfig } from "@/types/llm_config";
|
||||
import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
|
||||
|
||||
|
|
@ -173,11 +173,10 @@ const SettingsPage = () => {
|
|||
<button
|
||||
onClick={handleSaveConfig}
|
||||
disabled={buttonState.isDisabled}
|
||||
className={`w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${
|
||||
buttonState.isDisabled
|
||||
className={`w-full font-semibold py-3 px-4 rounded-lg transition-all duration-500 ${buttonState.isDisabled
|
||||
? "bg-gray-400 cursor-not-allowed"
|
||||
: "bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 focus:ring-4 focus:ring-blue-200"
|
||||
} text-white`}
|
||||
} text-white`}
|
||||
>
|
||||
{buttonState.isLoading ? (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { ArrowLeft, Home, Loader2, Trash2 } from "lucide-react";
|
|||
import { useFontLoader } from "../../hooks/useFontLoader";
|
||||
import { MixpanelEvent, trackEvent } from "@/utils/mixpanel";
|
||||
import TemplateService from "../../services/api/template";
|
||||
import Header from "../../dashboard/components/Header";
|
||||
import Header from "../../../(dashboard)/dashboard/components/Header";
|
||||
import { toast } from "sonner";
|
||||
import { CustomTemplateLayout, useCustomTemplateDetails } from "@/app/hooks/useCustomTemplates";
|
||||
import { templates as templateGroups, getTemplatesByTemplateName } from "@/app/presentation-templates";
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
CustomTemplates,
|
||||
} from "@/app/hooks/useCustomTemplates";
|
||||
import { CompiledLayout } from "@/app/hooks/compileLayout";
|
||||
import Header from "../dashboard/components/Header";
|
||||
import Header from "../../(dashboard)/dashboard/components/Header";
|
||||
|
||||
// Component for rendering custom template card with lazy-loaded previews
|
||||
const CustomTemplateCard = ({ template }: { template: CustomTemplates }) => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import Header from "@/app/(presentation-generator)/dashboard/components/Header";
|
||||
import Header from "@/app/(dashboard)/dashboard/components/Header";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import React from "react";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from "react";
|
||||
|
||||
import UploadPage from "./components/UploadPage";
|
||||
import Header from "@/app/(presentation-generator)/dashboard/components/Header";
|
||||
import Header from "@/app/(dashboard)/dashboard/components/Header";
|
||||
import { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
5311
servers/nextjs/public/card_bg.svg
Normal file
5311
servers/nextjs/public/card_bg.svg
Normal file
File diff suppressed because it is too large
Load diff
|
After Width: | Height: | Size: 1,021 KiB |
BIN
servers/nextjs/public/create_presentation.png
Normal file
BIN
servers/nextjs/public/create_presentation.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 227 KiB |
BIN
servers/nextjs/public/logo-with-bg.png
Normal file
BIN
servers/nextjs/public/logo-with-bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
Loading…
Add table
Reference in a new issue