feat: Outline and upload page design

This commit is contained in:
shiva raj badu 2026-02-22 19:16:16 +05:45
parent 45185fb125
commit 9e54c6bb9e
No known key found for this signature in database
8 changed files with 175 additions and 77 deletions

View file

@ -11,16 +11,16 @@ import { trackEvent, MixpanelEvent } from "@/utils/mixpanel";
const Header = () => {
const pathname = usePathname();
return (
<div className="w-full shadow-lg sticky top-0 z-50">
<div className="w-full sticky top-0 z-50 py-7">
<Wrapper>
<div className="flex items-center justify-between py-1">
<div className="flex items-center gap-3">
{(pathname !== "/upload" && pathname !== "/dashboard") && <BackBtn />}
<Link href="/dashboard" onClick={() => trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/dashboard" })}>
<img
src="/logo-white.png"
src="/Logo.png"
alt="Presentation logo"
className="h-16"
className="h-[33px]"
/>
</Link>
</div>
@ -29,7 +29,7 @@ const Header = () => {
href="/custom-template"
prefetch={false}
onClick={() => trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/custom-template" })}
className="flex items-center gap-2 px-3 py-2 text-white hover:bg-primary/80 rounded-md transition-colors outline-none"
className="flex items-center gap-2 px-3 py-2 text-[#101323] rounded-md transition-colors outline-none"
role="menuitem"
>
<FilePlus2 className="w-5 h-5" />
@ -39,7 +39,7 @@ const Header = () => {
href="/template-preview"
prefetch={false}
onClick={() => trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/template-preview" })}
className="flex items-center gap-2 px-3 py-2 text-white hover:bg-primary/80 rounded-md transition-colors outline-none"
className="flex items-center gap-2 px-3 py-2 text-[#101323] rounded-md transition-colors outline-none"
role="menuitem"
>
<Layout className="w-5 h-5" />

View file

@ -18,7 +18,7 @@ const HeaderNav = () => {
<Link
href="/dashboard"
prefetch={false}
className="flex items-center gap-2 px-3 py-2 text-white hover:bg-primary/80 rounded-md transition-colors outline-none"
className="flex items-center gap-2 px-3 py-2 text-[#101323] rounded-md transition-colors outline-none"
role="menuitem"
onClick={() => trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/dashboard" })}
>
@ -31,7 +31,7 @@ const HeaderNav = () => {
<Link
href="/settings"
prefetch={false}
className="flex items-center gap-2 px-3 py-2 text-white hover:bg-primary/80 rounded-md transition-colors outline-none"
className="flex items-center gap-2 px-3 py-2 text-[#101323] rounded-md transition-colors outline-none"
role="menuitem"
onClick={() => trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/settings" })}
>

View file

@ -32,22 +32,76 @@ export const CustomTemplateCard = memo(({ template, onSelectTemplate, selectedTe
const isSelected = selectedTemplate === template.id;
return (
// <Card
// className={`${isSelected ? 'border-2 border-blue-500' : ''} cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden relative`}
// style={{ contain: 'layout style paint' }}
// onClick={() => {
// onSelectTemplate(template.id);
// }}
// >
// <div className="p-5">
// <div className="flex items-center justify-between mb-2">
// <h3 className="text-xl font-bold text-gray-900">
// {template.name}
// </h3>
// </div>
// {/* Layout previews */}
// <div className="grid grid-cols-2 gap-2">
// {customLoading ? (
// // 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 && previewLayouts?.length > 0 ? (
// // Actual layout previews - using memoized component
// previewLayouts?.slice(0, 4).map((layout: CompiledLayout, index: number) => (
// <LayoutPreview
// key={`${template.id}-preview-${index}`}
// layout={layout}
// templateId={template.id}
// index={index}
// />
// ))
// ) : (
// // 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>
// {isSelected && (
// <div className="absolute top-0 right-0 bg-blue-500 text-white px-2 py-1 rounded-bl-lg">
// Selected
// </div>
// )}
// </Card>
<Card
className={`${isSelected ? 'border-2 border-blue-500' : ''} cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden relative`}
style={{ contain: 'layout style paint' }}
onClick={() => {
onSelectTemplate(template.id);
}}
className={`${isSelected ? 'border-2 border-blue-500' : ''} cursor-pointer flex flex-col justify-between relative hover:shadow-lg transition-all duration-200 group overflow-hidden`}
onClick={() => onSelectTemplate(template.id)}
>
<img src="/card_bg.svg" alt="" className="absolute top-0 left-0 w-full h-full object-cover" />
<span 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">
Layouts- {template.layoutCount}
</span>
<div className="p-5">
<div className="flex items-center justify-between mb-2">
<h3 className="text-xl font-bold text-gray-900">
{template.name}
</h3>
</div>
{/* Layout previews */}
<div className="grid grid-cols-2 gap-2">
@ -61,36 +115,37 @@ export const CustomTemplateCard = memo(({ template, onSelectTemplate, selectedTe
<Loader2 className="w-4 h-4 text-purple-300 animate-spin" />
</div>
))
) : previewLayouts && previewLayouts?.length > 0 ? (
// Actual layout previews - using memoized component
previewLayouts?.slice(0, 4).map((layout: CompiledLayout, index: number) => (
<LayoutPreview
key={`${template.id}-preview-${index}`}
layout={layout}
templateId={template.id}
index={index}
/>
))
) : (
// 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>
))
) : 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>
);
})
)}
</div>
</div>
{isSelected && (
<div className="absolute top-0 right-0 bg-blue-500 text-white px-2 py-1 rounded-bl-lg">
Selected
</div>
)}
<div className="flex items-center justify-between p-5 bg-white border-t border-[#EDEEEF] relative z-40 ">
<h3 className="text-sm font-bold text-gray-900">
{template.name}
</h3>
</div>
</Card>
);
});

View file

@ -91,8 +91,9 @@ const OutlineContent: React.FC<OutlineContentProps> = ({
)}
{/* Outlines content */}
{outlines && outlines.length > 0 && (
<div className="bg-[#F9F8F8] p-7 rounded-[20px]">
<div className="bg-[#F9F8F8] p-7 rounded-[20px] overflow-y-auto custom_scrollbar">
<DndContext
sensors={sensors}
collisionDetection={closestCenter}

View file

@ -16,6 +16,7 @@ import { useOutlineManagement } from "../hooks/useOutlineManagement";
import { usePresentationGeneration } from "../hooks/usePresentationGeneration";
import TemplateSelection from "./TemplateSelection";
import { TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils";
import { Separator } from "@/components/ui/separator";
const OutlinePage: React.FC = () => {
const { presentation_id, outlines } = useSelector(
@ -57,9 +58,10 @@ const OutlinePage: React.FC = () => {
>
Outline & Content
</TabsTrigger>
<Separator orientation="vertical" className="h-6 mx-1" />
<TabsTrigger
value={TABS.LAYOUTS}
className="relative rounded-full px-5 py-2 text-xs font-medium text-[#2D2D2D] shadow-none before:absolute before:left-0 before:top-1/2 before:h-6 before:w-px before:-translate-y-1/2 before:bg-[#D7D7D8] data-[state=active]:bg-[#E9E2F8] data-[state=active]:text-[#7E3AF2] data-[state=active]:shadow-none"
className="relative rounded-full px-5 py-2 text-xs font-medium text-[#2D2D2D] shadow-none data-[state=active]:bg-[#E9E2F8] data-[state=active]:text-[#7E3AF2] data-[state=active]:shadow-none"
>
Select Template
</TabsTrigger>

View file

@ -1,7 +1,8 @@
"use client";
import React, { useEffect } from "react";
import { templates, TemplateLayoutsWithSettings } from "@/app/presentation-templates";
import { templates } from "@/app/presentation-templates";
import { TemplateLayoutsWithSettings } from "@/app/presentation-templates/utils";
import { Card } from "@/components/ui/card";
import { TemplateWithData } from "@/app/presentation-templates/utils";
import { CustomTemplates, useCustomTemplateSummaries } from "@/app/hooks/useCustomTemplates";
@ -34,37 +35,26 @@ const TemplateSelection: React.FC<TemplateSelectionProps> = ({
const { templates: customTemplates, loading: customLoading } = useCustomTemplateSummaries();
return (
<div className="space-y-8 mb-4">
{/* In Built Templates */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-3">In Built Templates</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{templates.map((template: TemplateLayoutsWithSettings) => {
const previewLayouts = template.layouts.slice(0, 4);
return (
<Card
key={template.id}
className={`${typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id ? 'border-2 border-blue-500' : ''} cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden relative`}
className={`${typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id ? 'border-2 border-blue-500' : ''} cursor-pointer relative hover:shadow-lg transition-all duration-200 group overflow-hidden`}
onClick={() => onSelectTemplate(template)}
>
<span 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">
Layouts- {template.layouts.length}
</span>
<img src="/card_bg.svg" alt="" className="absolute top-0 left-0 w-full h-full object-cover" />
<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>
<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;
@ -72,12 +62,11 @@ const TemplateSelection: React.FC<TemplateSelectionProps> = ({
<div
key={`${template.id}-preview-${index}`}
className="relative bg-gray-100 border border-gray-200 overflow-hidden aspect-video rounded"
style={{ contain: 'layout style paint' }}
>
<div className="absolute inset-0 bg-transparent z-10" />
<div
className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]"
style={{ transform: 'scale(0.2) translateZ(0)', backfaceVisibility: 'hidden' }}
className="transform scale-[0.12] origin-top-left"
style={{ width: "833.33%", height: "833.33%" }}
>
<LayoutComponent data={layout.sampleData} />
</div>
@ -86,12 +75,63 @@ const TemplateSelection: React.FC<TemplateSelectionProps> = ({
})}
</div>
</div>
{typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id && (
<div className="absolute top-0 right-0 bg-blue-500 text-white px-2 py-1 rounded-bl-lg">
Selected
<div className="flex items-center justify-between p-5 bg-white border-t border-[#EDEEEF] relative z-40 ">
<div>
<h3 className="text-sm font-bold text-gray-900 capitalize">
{template.name}
</h3>
<p className="text-xs text-gray-600 mb-4 line-clamp-2">
{template.description}
</p>
</div>
)}
</div>
</Card>
// <Card
// key={template.id}
// className={`${typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id ? 'border-2 border-blue-500' : ''} cursor-pointer hover:shadow-lg transition-all duration-200 group overflow-hidden relative`}
// onClick={() => onSelectTemplate(template)}
// >
// <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>
// <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"
// style={{ contain: 'layout style paint' }}
// >
// <div className="absolute inset-0 bg-transparent z-10" />
// <div
// className="transform scale-[0.2] flex justify-center items-center origin-top-left w-[500%] h-[500%]"
// style={{ transform: 'scale(0.2) translateZ(0)', backfaceVisibility: 'hidden' }}
// >
// <LayoutComponent data={layout.sampleData} />
// </div>
// </div>
// );
// })}
// </div>
// </div>
// {typeof selectedTemplate !== 'string' && selectedTemplate?.id === template.id && (
// <div className="absolute top-0 right-0 bg-blue-500 text-white px-2 py-1 rounded-bl-lg">
// Selected
// </div>
// )}
// </Card>
);
})}
</div>
@ -115,7 +155,7 @@ const TemplateSelection: React.FC<TemplateSelectionProps> = ({
</p>
</Card>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="grid grid-cols-1 md:grid-cols-4 gap-6">
{customTemplates.map((template: CustomTemplates) => (
<CustomTemplateCard

View file

@ -196,7 +196,7 @@ const UploadPage = () => {
};
return (
<Wrapper className="pb-10 lg:max-w-[70%] xl:max-w-[65%]">
<Wrapper className=" pb-10 lg:max-w-[70%] xl:max-w-[65%] ">
<OverlayLoader
show={loadingState.isLoading}
text={loadingState.message}
@ -211,7 +211,7 @@ const UploadPage = () => {
onConfigChange={handleConfigChange}
/>
</div> */}
<div className=" w-full mx-auto px-2 md:px-0 ">
<div className=" w-full mx-auto px-2 md:px-0 max-w-[720px] ">
<div
className='fixed z-0 md:-bottom-[36%] -bottom-[40%] left-0 w-full h-full'

View file

@ -45,8 +45,8 @@ const page = () => {
return (
<div className="relative">
<Header />
<div className="flex flex-col items-center justify-center py-8">
<h1 className="text-3xl font-semibold font-instrument_sans text-[#101323] pb-3.5">
<div className="flex flex-col items-center justify-center my-10">
<h1 className="text-[64px] font-semibold font-instrument_sans text-[#101323] pb-3.5">
AI Presentation
</h1>
<p className="text-xl font-syne text-[#101323CC]">Choose a design, set preferences, and generate polished slides.</p>