feat: Outline and upload page design
This commit is contained in:
parent
45185fb125
commit
9e54c6bb9e
8 changed files with 175 additions and 77 deletions
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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" })}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue