303 lines
9.3 KiB
TypeScript
303 lines
9.3 KiB
TypeScript
"use client";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
SquareArrowOutUpRight,
|
|
Play,
|
|
Loader2,
|
|
Redo2,
|
|
Undo2,
|
|
FileCode2,
|
|
} from "lucide-react";
|
|
import React, { useState } from "react";
|
|
import Wrapper from "@/components/Wrapper";
|
|
import { useRouter } from "next/navigation";
|
|
import {
|
|
Popover,
|
|
PopoverContent,
|
|
PopoverTrigger,
|
|
} from "@/components/ui/popover";
|
|
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
|
import { apiFetch, apiUrl } from "@/lib/apiFetch";
|
|
import { OverlayLoader } from "@/components/ui/overlay-loader";
|
|
import { useDispatch, useSelector } from "react-redux";
|
|
|
|
import { RootState } from "@/store/store";
|
|
import { toast } from "sonner";
|
|
|
|
|
|
import Announcement from "@/components/Announcement";
|
|
import { PptxPresentationModel } from "@/types/pptx_models";
|
|
import HeaderNav from "../../components/HeaderNab";
|
|
import ReviewWorkflow from "../../components/ReviewWorkflow";
|
|
import PDFIMAGE from "@/public/pdf.svg";
|
|
import PPTXIMAGE from "@/public/pptx.svg";
|
|
import Image from "next/image";
|
|
import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo";
|
|
import ToolTip from "@/components/ToolTip";
|
|
import { clearPresentationData } from "@/store/slices/presentationGeneration";
|
|
import { clearHistory } from "@/store/slices/undoRedoSlice";
|
|
import Logo from "@/components/Logo";
|
|
import { TemplateCodegenExport } from "../../components/TemplateCodegenExport";
|
|
|
|
const Header = ({
|
|
presentation_id,
|
|
currentSlide,
|
|
}: {
|
|
presentation_id: string;
|
|
currentSlide?: number;
|
|
}) => {
|
|
const [open, setOpen] = useState(false);
|
|
const [showLoader, setShowLoader] = useState(false);
|
|
const [showTemplateCodegen, setShowTemplateCodegen] = useState(false);
|
|
const router = useRouter();
|
|
const dispatch = useDispatch();
|
|
|
|
|
|
const { presentationData, isStreaming } = useSelector(
|
|
(state: RootState) => state.presentationGeneration
|
|
);
|
|
|
|
const { onUndo, onRedo, canUndo, canRedo } = usePresentationUndoRedo();
|
|
|
|
const get_presentation_pptx_model = async (id: string): Promise<PptxPresentationModel> => {
|
|
const response = await apiFetch(`/api/presentation_to_pptx_model?id=${id}`);
|
|
const pptx_model = await response.json();
|
|
return pptx_model;
|
|
};
|
|
|
|
const handleExportPptx = async () => {
|
|
if (isStreaming) return;
|
|
|
|
try {
|
|
setOpen(false);
|
|
setShowLoader(true);
|
|
// Save the presentation data before exporting
|
|
await PresentationGenerationApi.updatePresentationContent(presentationData);
|
|
const pptx_model = await get_presentation_pptx_model(presentation_id);
|
|
if (!pptx_model) {
|
|
throw new Error("Failed to get presentation PPTX model");
|
|
}
|
|
const pptx_path = await PresentationGenerationApi.exportAsPPTX(pptx_model);
|
|
if (pptx_path) {
|
|
// window.open(pptx_path, '_self');
|
|
downloadLink(pptx_path);
|
|
} else {
|
|
throw new Error("No path returned from export");
|
|
}
|
|
} catch (error) {
|
|
console.error("Export failed:", error);
|
|
setShowLoader(false);
|
|
toast.error("Having trouble exporting!", {
|
|
description:
|
|
"We are having trouble exporting your presentation. Please try again.",
|
|
});
|
|
} finally {
|
|
setShowLoader(false);
|
|
}
|
|
};
|
|
|
|
const handleExportPdf = async () => {
|
|
if (isStreaming) return;
|
|
|
|
try {
|
|
setOpen(false);
|
|
setShowLoader(true);
|
|
// Save the presentation data before exporting
|
|
await PresentationGenerationApi.updatePresentationContent(presentationData);
|
|
|
|
const response = await apiFetch('/api/export-as-pdf', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
id: presentation_id,
|
|
title: presentationData?.title,
|
|
})
|
|
});
|
|
|
|
if (response.ok) {
|
|
const { path: pdfPath } = await response.json();
|
|
// window.open(pdfPath, '_blank');
|
|
downloadLink(pdfPath);
|
|
} else {
|
|
throw new Error("Failed to export PDF");
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error(err);
|
|
toast.error("Having trouble exporting!", {
|
|
description:
|
|
"We are having trouble exporting your presentation. Please try again.",
|
|
});
|
|
} finally {
|
|
setShowLoader(false);
|
|
}
|
|
};
|
|
const handleReGenerate = () => {
|
|
dispatch(clearPresentationData());
|
|
dispatch(clearHistory())
|
|
router.push(`/presentation?id=${presentation_id}&stream=true`);
|
|
};
|
|
const downloadLink = (path: string) => {
|
|
const url = apiUrl(path);
|
|
// if we have popup access give direct download if not redirect to the path
|
|
if (window.opener) {
|
|
window.open(url, '_blank');
|
|
} else {
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = url.split('/').pop() || 'download';
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
}
|
|
};
|
|
|
|
const ExportOptions = ({ mobile }: { mobile: boolean }) => (
|
|
<div className={`space-y-2 max-md:mt-4 ${mobile ? "" : "bg-white"} rounded-lg`}>
|
|
<Button
|
|
onClick={() => {
|
|
handleExportPdf();
|
|
}}
|
|
variant="ghost"
|
|
className={`pb-4 border-b rounded-none border-gray-300 w-full flex justify-start text-[#5146E5] ${mobile ? "bg-white py-6 border-none rounded-lg" : ""}`} >
|
|
<Image src={PDFIMAGE} alt="pdf export" width={30} height={30} />
|
|
Export as PDF
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
handleExportPptx();
|
|
}}
|
|
variant="ghost"
|
|
className={`pb-4 border-b rounded-none border-gray-300 w-full flex justify-start text-[#5146E5] ${mobile ? "bg-white py-6 border-none rounded-lg" : ""}`}
|
|
>
|
|
<Image src={PPTXIMAGE} alt="pptx export" width={30} height={30} />
|
|
Export as PPTX
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
setOpen(false);
|
|
setShowTemplateCodegen(true);
|
|
}}
|
|
variant="ghost"
|
|
className={`w-full flex justify-start text-[#5146E5] ${mobile ? "bg-white py-6" : ""}`}
|
|
>
|
|
<FileCode2 className="w-7 h-7 mr-2" />
|
|
Export from Template
|
|
</Button>
|
|
</div>
|
|
);
|
|
|
|
const MenuItems = ({ mobile }: { mobile: boolean }) => (
|
|
<div className="flex flex-col lg:flex-row items-center gap-4">
|
|
{/* undo redo */}
|
|
<button onClick={handleReGenerate} disabled={isStreaming || !presentationData} className="text-white disabled:opacity-50" >
|
|
|
|
Re-Generate
|
|
</button>
|
|
<div className="flex items-center gap-2 ">
|
|
<ToolTip content="Undo">
|
|
<button disabled={!canUndo} className="text-white disabled:opacity-50" onClick={() => {
|
|
onUndo();
|
|
}}>
|
|
|
|
<Undo2 className="w-6 h-6 " />
|
|
|
|
</button>
|
|
</ToolTip>
|
|
<ToolTip content="Redo">
|
|
|
|
<button disabled={!canRedo} className="text-white disabled:opacity-50" onClick={() => {
|
|
onRedo();
|
|
}}>
|
|
<Redo2 className="w-6 h-6 " />
|
|
|
|
</button>
|
|
</ToolTip>
|
|
|
|
</div>
|
|
|
|
{/* Review Status */}
|
|
<ReviewWorkflow presentationId={presentation_id} />
|
|
|
|
{/* Present Button */}
|
|
<Button
|
|
onClick={() => {
|
|
const to = `?id=${presentation_id}&mode=present&slide=${currentSlide || 0}`;
|
|
router.push(to);
|
|
}}
|
|
variant="ghost"
|
|
className="border border-white font-bold text-white rounded-[32px] transition-all duration-300 group"
|
|
>
|
|
<Play className="w-4 h-4 mr-1 stroke-white group-hover:stroke-black" />
|
|
Present
|
|
</Button>
|
|
|
|
{/* Desktop Export Button with Popover */}
|
|
|
|
<div style={{
|
|
zIndex: 100
|
|
}} className="hidden lg:block relative ">
|
|
<Popover open={open} onOpenChange={setOpen} >
|
|
<PopoverTrigger asChild>
|
|
<Button className={`border py-5 text-[#5146E5] font-bold rounded-[32px] transition-all duration-500 hover:border hover:bg-[#5146E5] hover:text-white w-full ${mobile ? "" : "bg-white"}`}>
|
|
<SquareArrowOutUpRight className="w-4 h-4 mr-1" />
|
|
Export
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent align="end" className="w-[250px] space-y-2 py-3 px-2 ">
|
|
<ExportOptions mobile={false} />
|
|
</PopoverContent>
|
|
</Popover>
|
|
</div>
|
|
|
|
{/* Mobile Export Section */}
|
|
<div className="lg:hidden flex flex-col w-full">
|
|
<ExportOptions mobile={true} />
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<>
|
|
<OverlayLoader
|
|
show={showLoader}
|
|
text="Exporting presentation..."
|
|
showProgress={true}
|
|
duration={40}
|
|
/>
|
|
<div
|
|
|
|
className="bg-[#5146E5] w-full shadow-lg sticky top-0 ">
|
|
|
|
<Announcement />
|
|
<Wrapper className="flex items-center justify-between py-1">
|
|
<Logo variant="light" className="min-w-[162px]" />
|
|
|
|
{/* Desktop Menu */}
|
|
<div className="hidden lg:flex items-center gap-4 2xl:gap-6">
|
|
{isStreaming && (
|
|
<Loader2 className="animate-spin text-white font-bold w-6 h-6" />
|
|
)}
|
|
|
|
|
|
<MenuItems mobile={false} />
|
|
<HeaderNav />
|
|
</div>
|
|
|
|
{/* Mobile Menu */}
|
|
<div className="lg:hidden flex items-center gap-4">
|
|
<HeaderNav />
|
|
|
|
</div>
|
|
</Wrapper>
|
|
|
|
</div>
|
|
{showTemplateCodegen && (
|
|
<TemplateCodegenExport
|
|
presentationId={presentation_id}
|
|
onClose={() => setShowTemplateCodegen(false)}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Header;
|