Chore(Nextjs): Layout Context
This commit is contained in:
parent
aa9ededb2b
commit
419074cb0e
23 changed files with 260 additions and 262 deletions
|
|
@ -0,0 +1,209 @@
|
|||
"use client";
|
||||
import React, { createContext, useContext, useEffect, useState, ReactNode } from 'react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import * as z from 'zod';
|
||||
|
||||
interface LayoutInfo {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
json_schema: any;
|
||||
}
|
||||
|
||||
interface LayoutContextType {
|
||||
layoutSchema: LayoutInfo[] | null;
|
||||
idMapFileNames: Record<string, string>;
|
||||
idMapSchema: Record<string, z.ZodSchema>;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
getLayout: (layoutId: string) => React.ComponentType<{ data: any }> | null;
|
||||
isPreloading: boolean;
|
||||
cacheSize: number;
|
||||
refetch: () => Promise<void>;
|
||||
}
|
||||
|
||||
const LayoutContext = createContext<LayoutContextType | undefined>(undefined);
|
||||
|
||||
// Global layout cache
|
||||
const layoutCache = new Map<string, React.ComponentType<{ data: any }>>();
|
||||
|
||||
export const LayoutProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [layoutSchema, setLayoutSchema] = useState<LayoutInfo[] | null>(null);
|
||||
const [idMapFileNames, setIdMapFileNames] = useState<Record<string, string>>({});
|
||||
const [idMapSchema, setIdMapSchema] = useState<Record<string, z.ZodSchema>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPreloading, setIsPreloading] = useState(false);
|
||||
|
||||
const extractSchema = async (layoutFiles: string[]) => {
|
||||
const layouts: LayoutInfo[] = [];
|
||||
const idMapFileNames: Record<string, string> = {};
|
||||
const idMapSchema: Record<string, z.ZodSchema> = {};
|
||||
|
||||
for (const fileName of layoutFiles) {
|
||||
try {
|
||||
const file = fileName.replace('.tsx', '').replace('.ts', '');
|
||||
const module = await import(`@/components/layouts/${file}`);
|
||||
|
||||
if (!module.default) {
|
||||
toast({
|
||||
title: `${file} has no default export`,
|
||||
description: 'Please ensure the layout file exports a default component',
|
||||
});
|
||||
console.warn(`${file} has no default export`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!module.Schema) {
|
||||
toast({
|
||||
title: `${file} has no Schema export`,
|
||||
description: 'Please ensure the layout file exports a Schema',
|
||||
});
|
||||
console.warn(`${file} has no Schema export`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const layoutId = module.layoutId;
|
||||
if (!layoutId) {
|
||||
toast({
|
||||
title: `${file} has no layoutId`,
|
||||
description: 'Please ensure the layout file exports a layoutId',
|
||||
});
|
||||
console.warn(`${file} has no layoutId`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const layoutName = module.layoutName;
|
||||
const layoutDescription = module.layoutDescription;
|
||||
const jsonSchema = z.toJSONSchema(module.Schema, {
|
||||
override: (ctx) => {
|
||||
delete ctx.jsonSchema.default;
|
||||
},
|
||||
});
|
||||
|
||||
const layout = {
|
||||
id: layoutId,
|
||||
name: layoutName,
|
||||
description: layoutDescription,
|
||||
json_schema: jsonSchema,
|
||||
};
|
||||
|
||||
idMapFileNames[layoutId] = fileName;
|
||||
idMapSchema[layoutId] = module.Schema;
|
||||
layouts.push(layout);
|
||||
} catch (error) {
|
||||
console.error(`Error extracting schema for ${fileName}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return { layouts, idMapFileNames, idMapSchema };
|
||||
};
|
||||
|
||||
const loadLayouts = async () => {
|
||||
if (layoutSchema) return; // Already loaded
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const layoutResponse = await fetch('/api/layouts');
|
||||
const layoutFiles = await layoutResponse.json();
|
||||
const response = await extractSchema(layoutFiles);
|
||||
|
||||
setLayoutSchema(response?.layouts || []);
|
||||
setIdMapFileNames(response?.idMapFileNames || {});
|
||||
setIdMapSchema(response?.idMapSchema || {});
|
||||
|
||||
// Preload layouts after loading schema
|
||||
await preloadLayouts(response?.idMapFileNames || {});
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to load layouts';
|
||||
setError(errorMessage);
|
||||
console.error('Error loading layouts:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const preloadLayouts = async (fileNames: Record<string, string>) => {
|
||||
setIsPreloading(true);
|
||||
|
||||
try {
|
||||
const layoutPromises = Object.values(fileNames).map(async (layoutName) => {
|
||||
if (!layoutCache.has(layoutName)) {
|
||||
const Layout = dynamic(
|
||||
() => import(`@/components/layouts/${layoutName}`),
|
||||
{
|
||||
loading: () => <div className="w-full aspect-[16/9] bg-gray-100 animate-pulse rounded-lg" />,
|
||||
ssr: false,
|
||||
}
|
||||
) as React.ComponentType<{ data: any }>;
|
||||
|
||||
layoutCache.set(layoutName, Layout);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(layoutPromises);
|
||||
} catch (error) {
|
||||
console.error('Error preloading layouts:', error);
|
||||
} finally {
|
||||
setIsPreloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getLayout = (layoutId: string): React.ComponentType<{ data: any }> | null => {
|
||||
const layoutName = idMapFileNames[layoutId];
|
||||
if (!layoutName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return cached layout if available
|
||||
if (layoutCache.has(layoutName)) {
|
||||
return layoutCache.get(layoutName)!;
|
||||
}
|
||||
|
||||
// Create and cache layout if not available
|
||||
const Layout = dynamic(
|
||||
() => import(`@/components/layouts/${layoutName}`),
|
||||
{
|
||||
loading: () => <div className="w-full aspect-[16/9] bg-gray-100 animate-pulse rounded-lg" />,
|
||||
ssr: false,
|
||||
}
|
||||
) as React.ComponentType<{ data: any }>;
|
||||
|
||||
layoutCache.set(layoutName, Layout);
|
||||
return Layout;
|
||||
};
|
||||
|
||||
// Load layouts on mount
|
||||
useEffect(() => {
|
||||
loadLayouts();
|
||||
}, []);
|
||||
|
||||
const contextValue: LayoutContextType = {
|
||||
layoutSchema,
|
||||
idMapFileNames,
|
||||
idMapSchema,
|
||||
loading,
|
||||
error,
|
||||
getLayout,
|
||||
isPreloading,
|
||||
cacheSize: layoutCache.size,
|
||||
refetch: loadLayouts,
|
||||
};
|
||||
|
||||
return (
|
||||
<LayoutContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</LayoutContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useLayout = (): LayoutContextType => {
|
||||
const context = useContext(LayoutContext);
|
||||
if (context === undefined) {
|
||||
throw new Error('useLayout must be used within a LayoutProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
|
@ -29,7 +29,7 @@ import { getIconFromFile } from "../../utils/others";
|
|||
import { ChevronRight, PanelRightOpen, X } from "lucide-react";
|
||||
import ToolTip from "@/components/ToolTip";
|
||||
import Header from "@/app/dashboard/components/Header";
|
||||
import useLayoutSchema from "../../hooks/useLayoutSchema";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
||||
// Types
|
||||
interface LoadingState {
|
||||
|
|
@ -70,7 +70,7 @@ const DocumentsPreviewPage: React.FC = () => {
|
|||
duration: 10,
|
||||
progress: false,
|
||||
});
|
||||
const { layoutSchema } = useLayoutSchema();
|
||||
const { layoutSchema } = useLayout();
|
||||
|
||||
// Memoized computed values
|
||||
const fileItems: FileItem[] = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -1,86 +0,0 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import useLayoutSchema from "./useLayoutSchema";
|
||||
|
||||
// Global layout cache to persist across component unmounts
|
||||
const layoutCache = new Map<string, React.ComponentType<{ data: any }>>();
|
||||
|
||||
const useLayoutCache = () => {
|
||||
const { idMapFileNames, loading } = useLayoutSchema();
|
||||
const [isPreloading, setIsPreloading] = useState(false);
|
||||
const preloadedRef = useRef(false);
|
||||
|
||||
// Pre-load all layouts when schema is available
|
||||
useEffect(() => {
|
||||
if (!loading && idMapFileNames && Object.keys(idMapFileNames).length > 0 && !preloadedRef.current) {
|
||||
preloadLayouts();
|
||||
preloadedRef.current = true;
|
||||
}
|
||||
}, [idMapFileNames, loading]);
|
||||
|
||||
const preloadLayouts = async () => {
|
||||
if (isPreloading) return;
|
||||
|
||||
setIsPreloading(true);
|
||||
|
||||
try {
|
||||
const layoutPromises = Object.values(idMapFileNames).map(async (layoutName) => {
|
||||
if (!layoutCache.has(layoutName)) {
|
||||
const Layout = dynamic(
|
||||
() => import(`@/components/layouts/${layoutName}`),
|
||||
{
|
||||
loading: () => <div className="w-full aspect-[16/9] bg-gray-100 animate-pulse rounded-lg" />,
|
||||
ssr: false,
|
||||
}
|
||||
) as React.ComponentType<{ data: any }>;
|
||||
|
||||
layoutCache.set(layoutName, Layout);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(layoutPromises);
|
||||
} catch (error) {
|
||||
console.error('Error preloading layouts:', error);
|
||||
} finally {
|
||||
setIsPreloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getLayout = (layoutId: string): React.ComponentType<{ data: any }> | null => {
|
||||
const layoutName = idMapFileNames[layoutId];
|
||||
if (!layoutName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return cached layout if available
|
||||
if (layoutCache.has(layoutName)) {
|
||||
return layoutCache.get(layoutName)!;
|
||||
}
|
||||
|
||||
// Create and cache layout if not available
|
||||
const Layout = dynamic(
|
||||
() => import(`@/components/layouts/${layoutName}`),
|
||||
{
|
||||
loading: () => <div className="w-full aspect-[16/9] bg-gray-100 animate-pulse rounded-lg" />,
|
||||
ssr: false,
|
||||
}
|
||||
) as React.ComponentType<{ data: any }>;
|
||||
|
||||
layoutCache.set(layoutName, Layout);
|
||||
return Layout;
|
||||
};
|
||||
|
||||
const clearCache = () => {
|
||||
layoutCache.clear();
|
||||
preloadedRef.current = false;
|
||||
};
|
||||
|
||||
return {
|
||||
getLayout,
|
||||
isPreloading,
|
||||
clearCache,
|
||||
cacheSize: layoutCache.size,
|
||||
};
|
||||
};
|
||||
|
||||
export default useLayoutCache;
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import { useState, useEffect } from "react";
|
||||
import { toast } from "@/hooks/use-toast";
|
||||
import * as z from 'zod';
|
||||
interface LayoutInfo {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
json_schema: any;
|
||||
}
|
||||
|
||||
// interface LayoutStructure {
|
||||
// name: string;
|
||||
// ordered: boolean;
|
||||
// slides: LayoutInfo[];
|
||||
// }
|
||||
|
||||
const useLayoutSchema = () => {
|
||||
const [layoutSchema, setLayoutSchema] = useState<LayoutInfo[] | null>(null);
|
||||
const [idMapFileNames, setIdMapFileNames] = useState<Record<string, string>>({});
|
||||
const [idMapSchema, setIdMapSchema] = useState<Record<string, z.ZodSchema>>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadLayouts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const layoutResponse = await fetch('/api/layouts');
|
||||
const layoutFiles = await layoutResponse.json();
|
||||
const response = await extractSchema(layoutFiles);
|
||||
|
||||
setLayoutSchema(response?.layouts || []);
|
||||
setIdMapFileNames(response?.idMapFileNames || {});
|
||||
setIdMapSchema(response?.idMapSchema || {});
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Failed to load layouts';
|
||||
setError(errorMessage);
|
||||
console.error('Error loading layouts:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Auto-load layouts on mount
|
||||
useEffect(() => {
|
||||
loadLayouts();
|
||||
}, []);
|
||||
|
||||
return {
|
||||
layoutSchema,
|
||||
setLayoutSchema,
|
||||
loading,
|
||||
error,
|
||||
refetch: loadLayouts,
|
||||
idMapFileNames,
|
||||
idMapSchema
|
||||
};
|
||||
};
|
||||
|
||||
export default useLayoutSchema;
|
||||
|
||||
|
||||
const extractSchema = async (layoutFiles: string[]) => {
|
||||
const layouts: LayoutInfo[] = [];
|
||||
const idMapFileNames: Record<string, string> = {};
|
||||
const idMapSchema: Record<string, z.ZodSchema> = {};
|
||||
for (const fileName of layoutFiles) {
|
||||
try {
|
||||
const file = fileName.replace('.tsx', '').replace('.ts', '')
|
||||
const module = await import(`@/components/layouts/${file}`)
|
||||
if (!module.default) {
|
||||
toast({
|
||||
title: `${file} has no default export`,
|
||||
description: 'Please ensure the layout file exports a default component',
|
||||
})
|
||||
console.warn(`${file} has no default export`)
|
||||
return
|
||||
}
|
||||
if (!module.Schema) {
|
||||
toast({
|
||||
title: `${file} has no Schema export`,
|
||||
description: 'Please ensure the layout file exports a Schema',
|
||||
})
|
||||
console.warn(`${file} has no Schema export`)
|
||||
return
|
||||
}
|
||||
const layoutId = module.layoutId
|
||||
if(!layoutId) {
|
||||
toast({
|
||||
title: `${file} has no layoutId`,
|
||||
description: 'Please ensure the layout file exports a layoutId',
|
||||
})
|
||||
console.warn(`${file} has no layoutId`)
|
||||
return
|
||||
}
|
||||
const layoutName = module.layoutName
|
||||
const layoutDescription = module.layoutDescription
|
||||
const jsonSchema = z.toJSONSchema(module.Schema,{
|
||||
override :(ctx)=>{
|
||||
delete ctx.jsonSchema.default
|
||||
},
|
||||
})
|
||||
const layout = {
|
||||
id: layoutId,
|
||||
name: layoutName,
|
||||
description: layoutDescription,
|
||||
json_schema: jsonSchema,
|
||||
}
|
||||
idMapFileNames[layoutId] = fileName
|
||||
idMapSchema[layoutId] = module.Schema
|
||||
layouts.push(layout)
|
||||
} catch (error) {
|
||||
console.error(`Error extracting schema for ${fileName}:`, error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
return {layouts, idMapFileNames, idMapSchema}
|
||||
};
|
||||
|
|
@ -260,18 +260,6 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
try {
|
||||
const data = await DashboardApi.getPresentation(presentation_id);
|
||||
if (data) {
|
||||
if (data.presentation.theme) {
|
||||
dispatch(
|
||||
setThemeColors({
|
||||
...data.presentation.theme.colors,
|
||||
theme: data.presentation.theme.name as ThemeType,
|
||||
})
|
||||
);
|
||||
setColorsVariables(
|
||||
data.presentation.theme.colors,
|
||||
data.presentation.theme.name as ThemeType
|
||||
);
|
||||
}
|
||||
dispatch(setPresentationData(data));
|
||||
setLoading(false);
|
||||
}
|
||||
|
|
@ -393,8 +381,8 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => {
|
|||
/>
|
||||
<div className="flex-1 h-[calc(100vh-100px)] overflow-y-auto">
|
||||
<div
|
||||
className="mx-auto flex flex-col items-center overflow-hidden justify-center p-2 sm:p-6 pt-0 slide-theme"
|
||||
data-theme={currentTheme}
|
||||
className="mx-auto flex flex-col items-center overflow-hidden justify-center p-2 sm:p-6 pt-0 "
|
||||
|
||||
>
|
||||
{!presentationData ||
|
||||
loading ||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import { setPresentationData } from "@/store/slices/presentationGeneration";
|
||||
import { SortableSlide } from "./SortableSlide";
|
||||
import { SortableListItem } from "./SortableListItem";
|
||||
import useLayoutCache from "../../hooks/useLayoutCache";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
||||
interface SidePanelProps {
|
||||
selectedSlide: number;
|
||||
|
|
@ -50,7 +50,7 @@ const SidePanel = ({
|
|||
);
|
||||
console.log('presentationData', presentationData)
|
||||
const dispatch = useDispatch();
|
||||
const { getLayout } = useLayoutCache();
|
||||
const { getLayout } = useLayout();
|
||||
|
||||
// Memoized slide renderer using layout cache
|
||||
const renderSlideContent = useMemo(() => {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||
import { addSlide, updateSlide } from "@/store/slices/presentationGeneration";
|
||||
import NewSlide from "../../components/slide_layouts/NewSlide";
|
||||
import { getEmptySlideContent } from "../../utils/NewSlideContent";
|
||||
import useLayoutCache from "../../hooks/useLayoutCache";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
||||
interface SlideContentProps {
|
||||
slide: any;
|
||||
|
|
@ -37,13 +37,15 @@ const SlideContent = ({
|
|||
const { presentationData, isStreaming } = useSelector(
|
||||
(state: RootState) => state.presentationGeneration
|
||||
);
|
||||
const { getLayout } = useLayoutCache();
|
||||
const { getLayout, loading } = useLayout();
|
||||
|
||||
// Memoized layout component to prevent re-renders
|
||||
const LayoutComponent = useMemo(() => {
|
||||
const Layout = getLayout(slide.layout);
|
||||
if (!Layout) {
|
||||
return () => <div>Layout not found</div>;
|
||||
return () => <div className="flex flex-col items-center justify-center h-full">
|
||||
Layout not found
|
||||
</div>;
|
||||
}
|
||||
return Layout;
|
||||
}, [slide.layout, getLayout]);
|
||||
|
|
@ -122,7 +124,7 @@ const SlideContent = ({
|
|||
) {
|
||||
// Scroll to the last slide (newly generated during streaming)
|
||||
const lastSlideIndex = presentationData.slides.length - 1;
|
||||
const slideElement = document.getElementById(`slide-${presentationData.slides[lastSlideIndex].id}`);
|
||||
const slideElement = document.getElementById(`slide-${presentationData.slides[lastSlideIndex].index}`);
|
||||
if (slideElement) {
|
||||
slideElement.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
|
|
@ -140,7 +142,7 @@ const SlideContent = ({
|
|||
return (
|
||||
<>
|
||||
<div
|
||||
id={`slide-${slide.id}`}
|
||||
id={`slide-${slide.index}`}
|
||||
className=" w-full max-w-[1280px] main-slide flex items-center max-md:mb-4 justify-center relative"
|
||||
>
|
||||
{isStreaming && (
|
||||
|
|
@ -148,7 +150,9 @@ const SlideContent = ({
|
|||
)}
|
||||
<div className={` w-full group `}>
|
||||
{/* render slides */}
|
||||
{slideContent}
|
||||
{loading ? <div className="flex flex-col bg-white aspect-video items-center justify-center h-full">
|
||||
<Loader2 className="w-8 h-8 animate-spin" />
|
||||
</div> : slideContent}
|
||||
|
||||
{!showNewSlideSelection && (
|
||||
<div className="group-hover:opacity-100 hidden md:block opacity-0 transition-opacity my-4 duration-300">
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function SortableListItem({ slide, index, selectedSlide, onSlideClick }:
|
|||
|
||||
// If the mouse was down for less than 200ms, consider it a click
|
||||
if (timeDiff < 200 && !isDragging) {
|
||||
onSlideClick(slide.id);
|
||||
onSlideClick(slide.index);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ export function SortableListItem({ slide, index, selectedSlide, onSlideClick }:
|
|||
{...listeners}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
className={`p-3 cursor-pointer rounded-lg slide-box
|
||||
className={`p-3 cursor-pointer ring-0 border-[3px] rounded-lg slide-box
|
||||
${selectedSlide === index
|
||||
? 'ring-2 ring-[#5141e5] text-white'
|
||||
: 'hover:slide-box/40'
|
||||
? ' border-[#5141e5] '
|
||||
: 'hover:slide-box/40 border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<span className="font-medium slide-title">Slide {index + 1}</span>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
|
|||
|
||||
// If the mouse was down for less than 200ms, consider it a click
|
||||
if (timeDiff < 200 && !isDragging) {
|
||||
onSlideClick(slide.id);
|
||||
onSlideClick(slide.index);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export function SortableSlide({ slide, index, selectedSlide, onSlideClick, rende
|
|||
{...listeners}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
className={` cursor-pointer border-[3px] p-1 shadow-lg rounded-md transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-color'
|
||||
className={` cursor-pointer border-[3px] p-1 shadow-lg rounded-md transition-all duration-200 ${selectedSlide === index ? ' border-[#5141e5]' : 'border-gray-300'
|
||||
}`}
|
||||
>
|
||||
<div className=" slide-box relative overflow-hidden aspect-video">
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import { PresentationGenerationApi } from "../../services/api/presentation-gener
|
|||
import { OverlayLoader } from "@/components/ui/overlay-loader";
|
||||
import Wrapper from "@/components/Wrapper";
|
||||
import { setPptGenUploadState } from "@/store/slices/presentationGenUpload";
|
||||
import useLayoutSchema from "../../hooks/useLayoutSchema";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
|
||||
// Types for loading state
|
||||
interface LoadingState {
|
||||
|
|
@ -40,7 +40,7 @@ const UploadPage = () => {
|
|||
const router = useRouter();
|
||||
const dispatch = useDispatch();
|
||||
const { toast } = useToast();
|
||||
const { layoutSchema, loading: layoutsLoading, error: layoutsError } = useLayoutSchema();
|
||||
const { layoutSchema, loading: layoutsLoading, error: layoutsError } = useLayout();
|
||||
|
||||
// State management
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
|
|
|
|||
|
|
@ -39,18 +39,18 @@ export const metadata: Metadata = {
|
|||
}
|
||||
|
||||
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'>Create Presentation </h1>
|
||||
{/* <p className='text-sm text-gray-500'>We will generate a presentation for you</p> */}
|
||||
</div>
|
||||
<UploadPage />
|
||||
</div>)
|
||||
|
||||
<UploadPage />
|
||||
|
||||
</div>)
|
||||
}
|
||||
|
||||
export default page
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import {
|
||||
getHeader,
|
||||
getHeaderForFormData,
|
||||
} from "@/app/(presentation-generator)/services/api/header";
|
||||
|
||||
|
||||
|
|
@ -28,7 +27,7 @@ export class DashboardApi {
|
|||
static async getPresentations(): Promise<PresentationResponse[]> {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/ppt/user_presentations`,
|
||||
`/api/v1/ppt/presentation/all`,
|
||||
{
|
||||
method: "GET",
|
||||
}
|
||||
|
|
@ -49,7 +48,7 @@ export class DashboardApi {
|
|||
static async getPresentation(id: string) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/ppt/presentation?presentation_id=${id}`,
|
||||
`/api/v1/ppt/presentation/?id=${id}`,
|
||||
{
|
||||
method: "GET",
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ export class DashboardApi {
|
|||
static async deletePresentation(presentation_id: string) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`/api/v1/ppt/delete?presentation_id=${presentation_id}`,
|
||||
`/api/v1/ppt/delete?id=${presentation_id}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: getHeader(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import "./globals.css";
|
|||
import { Providers } from "./providers";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { FooterProvider } from "./(presentation-generator)/context/footerContext";
|
||||
import { LayoutProvider } from "./(presentation-generator)/context/LayoutContext";
|
||||
|
||||
const fraunces = Fraunces({
|
||||
subsets: ["latin"],
|
||||
|
|
@ -102,10 +103,13 @@ export default function RootLayout({
|
|||
className={`$ ${inter.variable} ${fraunces.variable} ${montserrat.variable} ${inria_serif.variable} ${roboto.variable} ${instrument_sans.variable} antialiased`}
|
||||
>
|
||||
<Providers>
|
||||
<FooterProvider>
|
||||
<LayoutProvider>
|
||||
<FooterProvider>
|
||||
|
||||
{children}
|
||||
</FooterProvider>
|
||||
|
||||
{children}
|
||||
</FooterProvider>
|
||||
</LayoutProvider>
|
||||
</Providers>
|
||||
<Toaster />
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex items-center aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex items-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-8 md:gap-12 lg:gap-16 w-full">
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
|
|||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative gap-4 lg:gap-8 mt-4 lg:mt-12">
|
||||
<div className="grid grid-cols-1 bg-white lg:grid-cols-2 relative gap-4 lg:gap-8 mt-4 lg:mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
|
@ -79,7 +79,7 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
|
|||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row mt-4 lg:mt-12 w-full relative gap-4 lg:gap-8">
|
||||
<div className="flex flex-col lg:flex-row bg-white mt-4 lg:mt-12 w-full relative gap-4 lg:gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
|
|
@ -107,7 +107,7 @@ const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ dat
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ dat
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container shadow-lg rounded-sm w-full max-w-[1280px] px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] font-inter flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" shadow-lg rounded-sm w-full max-w-[1280px] px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] font-inter flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center mb-4 lg:mb-16 w-full">
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container font-inter rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{slideData?.title || 'Chart Analysis'}
|
||||
</h1>
|
||||
|
||||
<div className={`flex w-full items-center ${isFullSizeGraph
|
||||
<div className={`flex w-full items-center ${isFullSizeGraph
|
||||
? "flex-col mt-4 lg:mt-10 gap-2 sm:gap-4 md:gap-6 lg:gap-10"
|
||||
: "mt-4 lg:mt-16 gap-4 sm:gap-8 md:gap-12 lg:gap-16"
|
||||
}`}>
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className="rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 sm:gap-18 md:gap-16 items-center w-full">
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData })
|
|||
|
||||
return (
|
||||
<div
|
||||
className="slide-container shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
className=" shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue