diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx index 540701bb..01e09750 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/dashboard/components/Header.tsx @@ -5,14 +5,39 @@ import React from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; +import { ArrowLeft } from "lucide-react"; + +const PATHS_WITH_HEADER_BACK = [ + "/upload", + "/outline", + "/documents-preview", + "/template-preview", +] as const; + +function pathMatches(pathname: string | null, base: string) { + return pathname === base || pathname?.startsWith(`${base}/`) === true; +} + const Header = () => { const pathname = usePathname(); + const showHeaderBack = PATHS_WITH_HEADER_BACK.some((p) => pathMatches(pathname, p)); + + const backToUpload = + pathMatches(pathname, "/outline") || pathMatches(pathname, "/documents-preview"); + const backToTemplates = pathMatches(pathname, "/template-preview"); + + const backHref = backToUpload ? "/upload" : backToTemplates ? "/templates" : "/dashboard"; + const backLabel = backToUpload + ? "Back to upload" + : backToTemplates + ? "Back to templates" + : "Go to your dashboard"; + return ( -
+
- {/* {(pathname !== "/upload" && pathname !== "/dashboard") && } */} trackEvent(MixpanelEvent.Navigation, { from: pathname, to: "/dashboard" })}> { />
+ {showHeaderBack ? ( +
+ + trackEvent(MixpanelEvent.Navigation, { from: pathname, to: backHref }) + } + > + + {backLabel} + +
+ ) : null}
diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx index f2286498..c1ca600a 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingSideBar.tsx @@ -35,14 +35,14 @@ const SettingSideBar = ({ mode, setMode, selectedProvider, setSelectedProvider }

Select Provider

{mode === 'presenton' &&
- -
- {/* Inbuilt Templates Section */} - {tab === 'default' &&
-
- {inbuiltTemplateCards} -
-
} + {/* Inbuilt Templates Section: non-neo first, then Report (neo) */} + {tab === 'default' && ( +
+
+ {nonNeoInbuilt.map((template) => ( + + ))} +
+ {neoInbuilt.length > 0 && ( +
+

+ Report +

+
+ {neoInbuilt.map((template) => ( + + ))} +
+
+ )} +
+ )} {tab === 'custom' &&
diff --git a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index dffaabf9..d7aa7478 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -7,7 +7,7 @@ import PresentationMode from "./PresentationMode"; import SidePanel from "./SidePanel"; import SlideContent from "./SlideContent"; import { Button } from "@/components/ui/button"; -import { usePathname } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; import { AlertCircle } from "lucide-react"; import { @@ -32,12 +32,8 @@ const PresentationPage: React.FC = ({ const [selectedSlide, setSelectedSlide] = useState(0); const [isFullscreen, setIsFullscreen] = useState(false); const [error, setError] = useState(false); + const router = useRouter(); - // Ensure /app_data and /static image paths resolve through FastAPI in Electron. - // useEffect(() => { - // const observer = setupImageUrlConverter(); - // return () => observer?.disconnect(); - // }, []); const { presentationData, isStreaming } = useSelector( @@ -124,7 +120,11 @@ const PresentationPage: React.FC = ({

We couldn't load your presentation. Please try again.

- +
+ + + +
); diff --git a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx index 67193c34..d1d1261a 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/presentation/components/SlideContent.tsx @@ -221,6 +221,11 @@ const SlideContent = ({ slide, index, presentationId }: SlideContentProps) => { className="min-h-[110px] max-h-[180px] w-full resize-none rounded-xl border border-gray-200 p-3 text-sm focus-visible:ring-1 focus-visible:ring-[#5141e5]" disabled={isUpdating} onChange={(e) => setEditPrompt(e.target.value)} + onKeyDown={(e) => { + if (e.key !== "Enter" || e.shiftKey || isUpdating) return; + e.preventDefault(); + handleSubmit(); + }} rows={5} wrap="soft" /> @@ -236,7 +241,7 @@ const SlideContent = ({ slide, index, presentationId }: SlideContentProps) => { - + {slide?.speaker_note && - - + {isCustom && ( -
+
-
+
{!isCustom && ( -
+
{staticTemplates.map((template: any, index: number) => { const LayoutComponent = template.component; return ( - -
+
-

+ + {index + 1 < 10 ? `0${index + 1}` : index + 1} + +

{template.layoutName}

-

+

{template.layoutDescription}

-
- + {/*
+ {template.layoutId} #{index + 1} -
+
*/}
-
+
{
- +
); })}
@@ -254,13 +237,13 @@ const GroupLayoutPreview = () => {
- + {templateParams}:{layout.layoutId}
-
+
{ })}
)} -
+
); }; diff --git a/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx b/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx index 19a1f54e..e5935aa9 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/upload/components/ConfigurationSelects.tsx @@ -6,7 +6,7 @@ import { SelectValue, } from "@/components/ui/select"; import { LanguageType, PresentationConfig, ToneType, VerbosityType } from "../type"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Check, ChevronsUp, ChevronsUpDown, ChevronUp, GalleryVertical, Languages, SlidersHorizontal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -46,15 +46,24 @@ const SLIDE_OPTIONS: SlideOption[] = ["5", "8", "9", "10", "11", "12", "13", "14 const SlideCountSelect: React.FC<{ value: string | null; onValueChange: (value: string) => void; -}> = ({ value, onValueChange }) => { + open: boolean; + onOpenChange: (open: boolean) => void; +}> = ({ value, onValueChange, open, onOpenChange }) => { const [customInput, setCustomInput] = useState( value && !SLIDE_OPTIONS.includes(value as SlideOption) ? value : "" ); + // useEffect(() => { + // if (value && !SLIDE_OPTIONS.includes(value as SlideOption)) { + // setCustomInput(value); + // } else { + // setCustomInput(""); + // } + // }, [value]); + const sanitizeToPositiveInteger = (raw: string): string => { const digitsOnly = raw.replace(/\D+/g, ""); if (!digitsOnly) return ""; - // Remove leading zeros const noLeadingZeros = digitsOnly.replace(/^0+/, ""); return noLeadingZeros; }; @@ -63,21 +72,32 @@ const SlideCountSelect: React.FC<{ const sanitized = sanitizeToPositiveInteger(customInput); if (sanitized && Number(sanitized) > 0) { onValueChange(sanitized); + onOpenChange(false); } }; + const displayLabel = value ? `${value} slides` : "Select Slides"; + return ( - + + + + {SLIDE_OPTIONS.map((option) => ( + { + onValueChange(option); + setCustomInput(""); + onOpenChange(false); + }} + className="font-syne text-sm font-medium" + > + + {option} slides + + ))} + + + + +
); }; @@ -147,17 +176,14 @@ const LanguageSelect: React.FC<{ name="language" data-testid="language-select" aria-expanded={open} - className="w-[180px] flex justify-between items-center gap-2 font-instrument_sans font-semibold overflow-hidden bg-white text-slate-700 h-10 rounded-xl px-3 ring-1 ring-inset ring-slate-200 shadow-sm" + className="w-[120px] overflow-hidden flex justify-between items-center gap-2 font-syne font-semibold bg-[#F6F6F9] text-slate-700 h-10 rounded-full px-3.5 ring-1 ring-inset ring-slate-200 shadow-sm" > - - - - - + + {value || "Select language"} - + @@ -200,6 +226,7 @@ export function ConfigurationSelects({ config, onConfigChange, }: ConfigurationSelectsProps) { + const [openSlides, setOpenSlides] = useState(false); const [openLanguage, setOpenLanguage] = useState(false); const [openAdvanced, setOpenAdvanced] = useState(false); @@ -241,6 +268,8 @@ export function ConfigurationSelects({ onConfigChange("slides", value)} + open={openSlides} + onOpenChange={setOpenSlides} /> handleOpenAdvancedChange(true)} - className="ml-auto flex items-center gap-2 text-sm bg-white text-slate-700 hover:bg-slate-50 focus-visible:ring-[#5146E5]/30 h-10 rounded-xl px-3 ring-1 ring-inset ring-slate-200 shadow-sm font-instrument_sans font-medium" + className="ml-auto flex items-center gap-2 text-sm bg-[#F6F6F9] text-slate-700 hover:bg-slate-50 focus-visible:ring-[#5146E5]/30 h-10 rounded-xl px-3 ring-1 ring-inset ring-slate-200 shadow-sm font-instrument_sans font-medium" data-testid="advanced-settings-button" >