'use client'
import Link from 'next/link'
import { usePathname, useSearchParams, useRouter } from 'next/navigation'
import { cn } from '@/lib/utils'
import {
Settings,
Plus,
ChevronRight,
Lock,
BookOpen,
Bot,
Inbox,
FlaskConical,
ArrowUpDown,
Archive,
MessageSquare,
Sparkles,
Trash2,
User,
LogOut,
Shield,
} from 'lucide-react'
import { useLanguage } from '@/lib/i18n'
import { useNotebooksQuery } from '@/lib/query-hooks'
import { useEffect, useMemo, useState } from 'react'
import { getAllNotes } from '@/app/actions/notes'
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
import { useNotebooks } from '@/context/notebooks-context'
import { Notebook, Note } from '@/lib/types'
import { motion, AnimatePresence } from 'motion/react'
import { getNoteDisplayTitle } from '@/lib/note-preview'
import { CreateNotebookDialog } from './create-notebook-dialog'
import { NotificationPanel } from './notification-panel'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { signOut } from 'next-auth/react'
type NavigationView = 'notebooks' | 'agents'
type SortOrder = 'newest' | 'oldest' | 'alpha'
function NoteLink({
title,
isActive,
onClick,
}: {
title: string
isActive: boolean
onClick: () => void
}) {
return (
{title}
)
}
function SidebarCarnetItem({
carnet,
isActive,
notes,
activeNoteId,
onCarnetClick,
onNoteClick,
}: {
carnet: { id: string; name: string; initial: string; isPrivate?: boolean }
isActive: boolean
/** Notes for this carnet — always passed (like architectural-grid ref); visibility toggled by isActive */
notes: { id: string; title: string }[]
activeNoteId: string | null
onCarnetClick: () => void
onNoteClick: (noteId: string, carnetId: string) => void
}) {
return (
{carnet.initial}
{carnet.name}
{carnet.isPrivate && }
{isActive && (
{notes.map(note => (
onNoteClick(note.id, carnet.id)}
/>
))}
{notes.length === 0 && (
No notes yet
)}
)}
)
}
export function Sidebar({ className, user }: { className?: string; user?: any }) {
const pathname = usePathname()
const searchParams = useSearchParams()
const router = useRouter()
const { t } = useLanguage()
const { notebooks } = useNotebooks()
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [notebookNotes, setNotebookNotes] = useState>({})
const [activeView, setActiveView] = useState('notebooks')
const [sortOrder, setSortOrder] = useState('newest')
const [showSortMenu, setShowSortMenu] = useState(false)
const currentNotebookId = searchParams.get('notebook')
const currentNoteId = searchParams.get('openNote')
// Determine if inbox is active (no notebook filter, on home page)
const isInboxActive =
pathname === '/' &&
!searchParams.get('notebook') &&
!searchParams.get('labels') &&
!searchParams.get('archived') &&
!searchParams.get('trashed')
// Sync toggle with route (fixes staying on "Agents" tab after navigating home)
useEffect(() => {
setActiveView(
pathname.startsWith('/agents') || pathname.startsWith('/lab') ? 'agents' : 'notebooks'
)
}, [pathname])
const displayName = user?.name || user?.email || ''
const initial = displayName ? displayName.charAt(0).toUpperCase() : '?'
const notebookIdsKey = useMemo(() => notebooks.map(nb => nb.id).sort().join(','), [notebooks])
/** Load note titles for every notebook (like ref: filter per carnet).
* Refetch when notebooks list changes (added/removed/reordered).
* Note: individual note changes (create/edit/delete) don't need to trigger this
* because React Query cache handles invalidation separately. */
useEffect(() => {
if (!notebookIdsKey) return
let cancelled = false
const load = async () => {
const mappedEntries = await Promise.all(
notebooks.map(async (nb: Notebook) => {
const notes = await getAllNotes(false, nb.id)
const mapped = notes.map((n: Note) => ({
id: n.id,
title: getNoteDisplayTitle(n, t('notes.untitled') || 'Untitled'),
}))
return [nb.id, mapped] as const
})
)
if (cancelled) return
setNotebookNotes(Object.fromEntries(mappedEntries))
}
load()
return () => {
cancelled = true
}
}, [notebookIdsKey, notebooks, t])
// BUG FIX: clicking a carnet always forces list (editorial) view
const handleCarnetClick = (notebookId: string) => {
const params = new URLSearchParams()
params.set('notebook', notebookId)
// forceList resets to editorial view in home-client
params.set('forceList', '1')
router.push(`/?${params.toString()}`)
}
const handleInboxClick = () => {
router.push('/?forceList=1')
}
const handleNoteClick = (noteId: string, notebookId: string) => {
const params = new URLSearchParams(searchParams.toString())
params.set('notebook', notebookId)
params.set('openNote', noteId)
params.delete('forceList')
router.push(`/?${params.toString()}`)
}
// Sort notebooks
const sortedNotebooks = [...notebooks].sort((a: Notebook, b: Notebook) => {
if (sortOrder === 'alpha') return a.name.localeCompare(b.name)
if (sortOrder === 'newest') return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
if (sortOrder === 'oldest') return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()
return 0
})
const sortLabels: Record = {
newest: t('sidebar.sortNewest') || 'Newest first',
oldest: t('sidebar.sortOldest') || 'Oldest first',
alpha: t('sidebar.sortAlpha') || 'A → Z',
}
return (
<>
>
)
}