Chore(Nextjs): Layout Context

This commit is contained in:
shiva raj badu 2025-07-17 01:41:51 +05:45
parent aa9ededb2b
commit 419074cb0e
23 changed files with 260 additions and 262 deletions

View file

@ -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;
};

View file

@ -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(() => {

View file

@ -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;

View file

@ -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}
};

View file

@ -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 ||

View file

@ -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(() => {

View file

@ -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">

View file

@ -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>

View file

@ -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">

View file

@ -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[]>([]);

View 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

View file

@ -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(),

View file

@ -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>

View file

@ -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">

View file

@ -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">

View file

@ -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">

View file

@ -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">

View file

@ -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">

View file

@ -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"
}`}>

View file

@ -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">

View file

@ -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">

View file

@ -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">

View file

@ -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 */}