| 1 | "use client"; |
| 2 | |
| 3 | import { Palette } from "lucide-react"; |
| 4 | import * as motion from "motion/react-client"; |
| 5 | import { useSession } from "next-auth/react"; |
| 6 | import Link from "next/link"; |
| 7 | import { usePathname } from "next/navigation"; |
| 8 | import { useEffect, useState } from "react"; |
| 9 | |
| 10 | // Import our new components |
| 11 | import { updatePresentationTitle } from "@/app/_actions/notebook/presentation/presentationActions"; |
| 12 | import AllweoneText from "@/components/globals/allweone-logo"; |
| 13 | import { ExportButton } from "@/components/presentation/buttons/ExportButton"; |
| 14 | import { PresentButton } from "@/components/presentation/buttons/PresentButton"; |
| 15 | import { PresentationMenu } from "@/components/presentation/controls/PresentationMenu"; |
| 16 | import { PresentationSavingIndicator } from "@/components/presentation/core/PresentationSavingIndicator"; |
| 17 | import { Button } from "@/components/ui/button"; |
| 18 | import { Brain } from "@/components/ui/icons"; |
| 19 | import { Input } from "@/components/ui/input"; |
| 20 | import { usePresentationState } from "@/states/presentation-state"; |
| 21 | |
| 22 | interface PresentationHeaderProps { |
| 23 | title?: string; |
| 24 | } |
| 25 | |
| 26 | export default function PresentationHeader({ title }: PresentationHeaderProps) { |
| 27 | const currentPresentationTitle = usePresentationState( |
| 28 | (s) => s.currentPresentationTitle, |
| 29 | ); |
| 30 | const isPresenting = usePresentationState((s) => s.isPresenting); |
| 31 | const currentPresentationId = usePresentationState( |
| 32 | (s) => s.currentPresentationId, |
| 33 | ); |
| 34 | const isReadOnly = usePresentationState((s) => s.isReadOnly); |
| 35 | const setActiveRightPanel = usePresentationState( |
| 36 | (s) => s.setActiveRightPanel, |
| 37 | ); |
| 38 | |
| 39 | const { status } = useSession(); |
| 40 | |
| 41 | const [presentationTitle, setPresentationTitle] = useState<string>( |
| 42 | "Presentation", |
| 43 | ); |
| 44 | const pathname = usePathname(); |
| 45 | |
| 46 | const isPresentationPage = |
| 47 | (pathname.startsWith("/presentation/") || |
| 48 | pathname.startsWith("/share/presentation/")) && |
| 49 | !pathname.includes("generate"); |
| 50 | const showPresentationTitle = pathname !== "/presentation"; |
| 51 | |
| 52 | const isLoggedOut = status === "unauthenticated"; |
| 53 | const showBrand = isLoggedOut; |
| 54 | |
| 55 | // Update title when it changes in the state |
| 56 | useEffect(() => { |
| 57 | if (currentPresentationTitle) { |
| 58 | setPresentationTitle(currentPresentationTitle); |
| 59 | } else if (title) { |
| 60 | setPresentationTitle(title); |
| 61 | } |
| 62 | }, [currentPresentationTitle, title]); |
| 63 | |
| 64 | if (pathname === "/presentation/create") |
| 65 | return ( |
| 66 | <header |
| 67 | className="notranslate flex min-h-12 w-full max-w-screen items-center justify-between gap-2 overflow-clip border-accent px-2 py-2" |
| 68 | translate="no" |
| 69 | > |
| 70 | <div className="flex min-w-0 items-center gap-2"> |
| 71 | {/* This component is suppose to be logo but for now its is actually hamburger menu */} |
| 72 | |
| 73 | <Link href="/presentation"> |
| 74 | <Button size={"icon"} className="rounded-full" variant={"ghost"}> |
| 75 | <Brain></Brain> |
| 76 | </Button> |
| 77 | </Link> |
| 78 | |
| 79 | <motion.div |
| 80 | initial={false} |
| 81 | layout="position" |
| 82 | transition={{ duration: 1 }} |
| 83 | > |
| 84 | <Link href="/" className="h-max"> |
| 85 | <AllweoneText className="h-10 w-30 cursor-pointer transition-transform duration-100 active:scale-95"></AllweoneText> |
| 86 | </Link> |
| 87 | </motion.div> |
| 88 | </div> |
| 89 | |
| 90 | {/* <SideBarDropdown /> */} |
| 91 | </header> |
| 92 | ); |
| 93 | |
| 94 | return ( |
| 95 | <header |
| 96 | className="notranslate flex min-h-12 w-full items-center justify-between gap-3 overflow-hidden border-b border-accent bg-background px-3 py-2 sm:px-4" |
| 97 | translate="no" |
| 98 | > |
| 99 | {/* Left section with breadcrumb navigation */} |
| 100 | <div className="flex min-w-0 flex-1 items-center gap-2"> |
| 101 | {showBrand ? ( |
| 102 | <Link href="/"> |
| 103 | <AllweoneText className="h-8 w-28" /> |
| 104 | </Link> |
| 105 | ) : ( |
| 106 | <Link |
| 107 | href="/presentation" |
| 108 | className="text-muted-foreground hover:text-foreground" |
| 109 | > |
| 110 | <Brain className="h-5 w-5"></Brain> |
| 111 | </Link> |
| 112 | )} |
| 113 | {isPresentationPage && !isLoggedOut && ( |
| 114 | <PresentationMenu readOnly={isReadOnly} /> |
| 115 | )} |
| 116 | {isLoggedOut && showPresentationTitle ? ( |
| 117 | <span className="truncate text-sm font-medium text-foreground sm:hidden"> |
| 118 | {presentationTitle} |
| 119 | </span> |
| 120 | ) : !isLoggedOut && showPresentationTitle ? ( |
| 121 | <Input |
| 122 | type="text" |
| 123 | id="presentation-title-input" |
| 124 | value={presentationTitle} |
| 125 | onChange={(e) => setPresentationTitle(e.target.value)} |
| 126 | disabled={isReadOnly} |
| 127 | onBlur={async () => { |
| 128 | if (isReadOnly) { |
| 129 | return; |
| 130 | } |
| 131 | if ( |
| 132 | presentationTitle && |
| 133 | currentPresentationTitle !== presentationTitle && |
| 134 | currentPresentationId |
| 135 | ) { |
| 136 | try { |
| 137 | await updatePresentationTitle( |
| 138 | currentPresentationId, |
| 139 | presentationTitle, |
| 140 | ); |
| 141 | } catch { |
| 142 | setPresentationTitle(currentPresentationTitle || ""); |
| 143 | } |
| 144 | } |
| 145 | }} |
| 146 | className="line-clamp-1 h-auto min-w-0 flex-1 cursor-text rounded-xs border-none bg-transparent p-0 font-medium text-ellipsis shadow-none outline-none sm:max-w-96" |
| 147 | style={{ |
| 148 | appearance: "none", |
| 149 | }} |
| 150 | /> |
| 151 | ) : null} |
| 152 | </div> |
| 153 | |
| 154 | {isLoggedOut && showPresentationTitle ? ( |
| 155 | <div className="pointer-events-none absolute top-1/2 left-1/2 w-[min(60vw,40rem)] -translate-x-1/2 -translate-y-1/2 px-3 text-center"> |
| 156 | <span className="line-clamp-1 text-lg font-medium text-foreground"> |
| 157 | {presentationTitle} |
| 158 | </span> |
| 159 | </div> |
| 160 | ) : null} |
| 161 | |
| 162 | {/* Right section with actions */} |
| 163 | <div className="scrollbar-hide flex max-w-[56vw] shrink-0 items-center gap-2 overflow-x-auto md:max-w-none md:overflow-visible"> |
| 164 | {/* Saving indicator - Placed right before Theme button */} |
| 165 | {isPresentationPage && !isPresenting && !isReadOnly && ( |
| 166 | <PresentationSavingIndicator /> |
| 167 | )} |
| 168 | |
| 169 | {/* Theme button - Only in presentation page, not outline or present mode */} |
| 170 | {isPresentationPage && !isPresenting && !isReadOnly && ( |
| 171 | <Button |
| 172 | variant="ghost" |
| 173 | className="h-9 gap-1.5" |
| 174 | onClick={() => setActiveRightPanel("theme")} |
| 175 | > |
| 176 | <Palette className="size-4" /> |
| 177 | <span className="sr-only"> |
| 178 | Theme |
| 179 | </span> |
| 180 | <span className="hidden sm:inline"> |
| 181 | Theme |
| 182 | </span> |
| 183 | </Button> |
| 184 | )} |
| 185 | |
| 186 | {/* Export button - Only in presentation page, not outline or present mode */} |
| 187 | {isPresentationPage && !isPresenting && !isReadOnly && <ExportButton />} |
| 188 | |
| 189 | {/* Present button - Only in presentation page, not outline */} |
| 190 | {isPresentationPage && <PresentButton />} |
| 191 | |
| 192 | </div> |
| 193 | </header> |
| 194 | ); |
| 195 | } |
| 196 |