diff --git a/nginx.conf b/nginx.conf index 9d4764aa..7198143e 100644 --- a/nginx.conf +++ b/nginx.conf @@ -16,6 +16,10 @@ http { location / { proxy_pass http://localhost:3000; + proxy_http_version 1.1; # Required for WebSocket + proxy_set_header Upgrade $http_upgrade; # WebSocket header + proxy_set_header Connection "upgrade"; # WebSocket header + proxy_set_header Host $host; proxy_read_timeout 30m; proxy_connect_timeout 30m; } diff --git a/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx b/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx index 5f0fbe2e..d960ad8d 100644 --- a/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/HeaderNab.tsx @@ -1,5 +1,5 @@ "use client"; -import { LayoutDashboard, Settings } from "lucide-react"; +import { LayoutDashboard, Settings, Upload } from "lucide-react"; import React from "react"; import Link from "next/link"; import { RootState } from "@/store/store"; @@ -11,6 +11,7 @@ const HeaderNav = () => { return (
+ { - if (editor && content !== editor.storage.markdown.getMarkdown()) { - editor.commands.setContent(content); - } - }, [content, editor]); + // useEffect(() => { + // if (editor && content !== editor.storage.markdown.getMarkdown()) { + // editor.commands.setContent(content); + // } + // }, [content, editor]); return (
diff --git a/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx b/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx deleted file mode 100644 index 6905aaca..00000000 --- a/servers/nextjs/app/(presentation-generator)/components/SlideFooter.tsx +++ /dev/null @@ -1,666 +0,0 @@ -import { - Sheet, - SheetContent, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet"; -import { Slider } from "@/components/ui/slider"; -import { Switch } from "@/components/ui/switch"; -import { Textarea } from "@/components/ui/textarea"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Button } from "@/components/ui/button"; -import { Label } from "@/components/ui/label"; -import React, { useRef, useState, useEffect } from "react"; -import { Camera, Loader2, Plus } from "lucide-react"; -import { useSelector } from "react-redux"; -import { RootState } from "@/store/store"; -import { getStaticFileUrl, isDarkColor } from "../../utils/others"; -import { defaultFooterProperties, useFooterContext } from "../../context/footerContext"; -import { FooterProperties } from "../../services/footerService"; -import { toast } from "sonner"; - -const SlideFooter: React.FC = () => { - const [showEditor, setShowEditor] = useState(false); - const [isUploading, setIsUploading] = useState({ - white: false, - dark: false, - }); - const { currentColors } = useSelector((state: RootState) => state.theme); - const isDark = isDarkColor(currentColors.slideBg); - - const whiteLogoRef = useRef(null); - const darkLogoRef = useRef(null); - - const { footerProperties, setFooterProperties, saveFooterProperties, resetFooterProperties, isPropertyChanged, setIsPropertyChanged } = useFooterContext(); - - - const handleSave = async () => { - await saveFooterProperties(footerProperties); - setIsPropertyChanged(false); - toast.success("Footer properties saved successfully"); - }; - - const handleReset = async () => { - await resetFooterProperties(); - setFooterProperties(defaultFooterProperties); - toast.success("Footer properties reset to default"); - }; - - const updateProperty = (path: string, value: any): void => { - setIsPropertyChanged(true); - const keys = path.split("."); - // Security: Validate path to prevent prototype pollution - const dangerousKeys = ['__proto__', 'constructor', 'prototype']; - if (keys.some(key => dangerousKeys.includes(key))) { - console.warn('Attempted prototype pollution with path:', path); - return; - } - - const allowedPaths = [ - 'logoProperties.showLogo', - 'logoProperties.logoPosition', - 'logoProperties.opacity', - 'logoProperties.logoImage.light', - 'logoProperties.logoImage.dark', - 'logoScale', - 'logoOffset.x', - 'logoOffset.y', - 'footerMessage.showMessage', - 'footerMessage.message', - 'footerMessage.fontSize', - 'footerMessage.opacity' - ] - - if (!allowedPaths.includes(path)) { - console.error(`Invalid path: ${path}`); - return; - } - - setFooterProperties((prevProps: FooterProperties) => { - const newProps = { ...prevProps }; - let current: any = newProps; - - for (let i = 0; i < keys.length - 1; i++) { - if (dangerousKeys.includes(keys[i])) { - console.warn('Attempted prototype pollution with path:', path); - return prevProps; - } - current[keys[i]] = { ...current[keys[i]] }; - current = current[keys[i]]; - } - - const finalKey = keys[keys.length - 1]; - if (dangerousKeys.includes(finalKey)) { - console.warn('Dangerous final key detected:', finalKey); - return prevProps; - } - current[keys[keys.length - 1]] = value; - return newProps; - }); - }; - - const handleSwitchChange = - (path: string) => - (checked: boolean): void => { - updateProperty(path, checked); - }; - - const handleTextChange = - (path: string) => - (e: React.ChangeEvent): void => { - updateProperty(path, e.target.value); - }; - - const handleSelectChange = - (path: string) => - (value: string): void => { - updateProperty(path, value); - }; - - const handleSliderChange = - (path: string) => - (value: number[]): void => { - updateProperty(path, value[0]); - }; - - const getLogoPositionClass = (): string => { - const { logoPosition } = footerProperties.logoProperties; - return logoPosition === "left" - ? "justify-start" - : logoPosition === "center" - ? "justify-center" - : "justify-end"; - }; - - const getLogoStyle = (): React.CSSProperties => { - const { opacity } = footerProperties.logoProperties; - const { logoScale, logoOffset } = footerProperties; - return { - opacity: opacity, - transform: `scale(${logoScale}) translate(${logoOffset.x}px, ${logoOffset.y}px)`, - }; - }; - - const getMessageStyle = (): React.CSSProperties => { - const { fontSize, opacity } = footerProperties.footerMessage; - return { - opacity: opacity, - fontSize: `${fontSize}px`, - fontFamily: currentColors.fontFamily || "Inter, sans-serif", - }; - }; - - const getLogoImageSrc = (): string => { - - if (isDark) { - return footerProperties.logoProperties.logoImage.dark; - } else { - return footerProperties.logoProperties.logoImage.light; - } - }; - - const convertToBase64 = (file: File): Promise => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result as string); - reader.onerror = (error) => reject(error); - }); - }; - - const handleWhiteLogoUpload = async ( - event: React.ChangeEvent - ) => { - setIsPropertyChanged(true); - const file = event.target.files?.[0]; - if (!file) return; - - if (!file.type.startsWith("image/")) { - toast.error("Please Upload An Image File"); - return; - } - - try { - setIsUploading({ ...isUploading, white: true }); - const base64String = await convertToBase64(file); - - setFooterProperties((prev: FooterProperties) => ({ - ...prev, - logoProperties: { - ...prev.logoProperties, - logoImage: { - ...prev.logoProperties.logoImage, - light: base64String, - }, - }, - })); - } catch (error) { - console.error("Error converting image:", error); - toast.error("Error uploading image"); - } finally { - setIsUploading({ ...isUploading, white: false }); - } - }; - - const handleDarkLogoUpload = async ( - event: React.ChangeEvent - ) => { - setIsPropertyChanged(true); - const file = event.target.files?.[0]; - if (!file) return; - - if (!file.type.startsWith("image/")) { - toast.error("Please Upload An Image File"); - return; - } - - try { - setIsUploading({ ...isUploading, dark: true }); - const base64String = await convertToBase64(file); - - setFooterProperties((prev: FooterProperties) => ({ - ...prev, - logoProperties: { - ...prev.logoProperties, - logoImage: { - ...prev.logoProperties.logoImage, - dark: base64String, - }, - }, - })); - } catch (error) { - console.error("Error converting image:", error); - toast.error("Error uploading image"); - } finally { - setIsUploading({ ...isUploading, dark: false }); - } - }; - - const getLocalImageUrl = (filePath: string) => { - if (!filePath) return ""; - if (filePath.startsWith('data:image')) return filePath; - return getStaticFileUrl(filePath); - }; - - const handleEditor = () => { - setShowEditor(!showEditor); - return; - }; - - const handleUploadClick = (isWhite: boolean) => { - if (isWhite) { - whiteLogoRef.current?.click(); - } else { - darkLogoRef.current?.click(); - } - }; - - const handleSheetClose = () => { - if (isPropertyChanged) { - toast.error("Unsaved Changes", { - description: "Please save changes before closing the editor", - }); - return; - } - setShowEditor(false); - }; - - return ( - <> - - - - e.preventDefault()} - className="sm:max-w-[500px] overflow-y-auto" - > - - Configure Footer -

- These changes will apply to all slides. -

-
- -
-
-

Logo Settings

- -
-
- -
-
-
-
handleUploadClick(true)} - className="h-28 border relative overflow-hidden flex justify-center items-center cursor-pointer group group-hover:border-blue-500" - > - - {isUploading.white ? ( -
- -
- ) : footerProperties.logoProperties.logoImage.light ? ( -
- brand white logo -
- -
-
- ) : ( - - )} -
-

Logo on Light

-
-
-
handleUploadClick(false)} - className="h-28 flex bg-black justify-center items-center border relative cursor-pointer group" - > - - {footerProperties.logoProperties.logoImage.dark && ( - brand white logo - )} - {isUploading.dark ? ( -
- -
- ) : footerProperties.logoProperties.logoImage.dark ? ( -
- brand white logo -
- -
-
- ) : ( - - )} -
-

Logo on Dark/Color

-
-
- - {footerProperties.logoProperties.showLogo && ( -
- - -
- )} - -
-
- - - {footerProperties.logoProperties.opacity} - -
- -
- -
-
- - - {footerProperties.logoScale} - -
- -
- -
-
- - - {footerProperties.logoOffset.x}px - -
- -
- -
-
- - - {footerProperties.logoOffset.y * -1}px - -
- -
-
-
- -
-

Footer Message

- -
-
- - -
-
- -