sepehr 8d95f34fcc fix: Add debounced Undo/Redo system to avoid character-by-character history
- Add debounced state updates for title and content (500ms delay)
- Immediate UI updates with delayed history saving
- Prevent one-letter-per-undo issue
- Add cleanup for debounce timers on unmount
2026-01-04 14:28:11 +01:00

137 lines
4.5 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Menu, Search, Archive, StickyNote, Tag, Moon, Sun } from 'lucide-react'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { cn } from '@/lib/utils'
import { searchNotes } from '@/app/actions/notes'
import { useRouter } from 'next/navigation'
export function Header() {
const [searchQuery, setSearchQuery] = useState('')
const [isSearching, setIsSearching] = useState(false)
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const pathname = usePathname()
const router = useRouter()
useEffect(() => {
// Check for saved theme or system preference
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | null
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
const initialTheme = savedTheme || systemTheme
setTheme(initialTheme)
document.documentElement.classList.toggle('dark', initialTheme === 'dark')
}, [])
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light'
setTheme(newTheme)
localStorage.setItem('theme', newTheme)
document.documentElement.classList.toggle('dark', newTheme === 'dark')
}
const handleSearch = async (query: string) => {
setSearchQuery(query)
if (query.trim()) {
setIsSearching(true)
// Search functionality will be handled by the parent component
// For now, we'll just update the URL
router.push(`/?search=${encodeURIComponent(query)}`)
setIsSearching(false)
} else {
router.push('/')
}
}
return (
<header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container flex h-16 items-center px-4 gap-4">
{/* Mobile Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className="md:hidden">
<Menu className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48">
<DropdownMenuItem asChild>
<Link href="/" className="flex items-center">
<StickyNote className="h-4 w-4 mr-2" />
Notes
</Link>
</DropdownMenuItem>
<DropdownMenuItem asChild>
<Link href="/archive" className="flex items-center">
<Archive className="h-4 w-4 mr-2" />
Archive
</Link>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* Logo */}
<Link href="/" className="flex items-center gap-2">
<StickyNote className="h-6 w-6 text-yellow-500" />
<span className="font-semibold text-xl hidden sm:inline-block">Memento</span>
</Link>
{/* Search Bar */}
<div className="flex-1 max-w-2xl">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="Search notes..."
className="pl-10"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
/>
</div>
</div>
{/* Theme Toggle */}
<Button variant="ghost" size="sm" onClick={toggleTheme}>
{theme === 'light' ? (
<Moon className="h-5 w-5" />
) : (
<Sun className="h-5 w-5" />
)}
</Button>
</div>
{/* Desktop Navigation */}
<nav className="hidden md:flex border-t">
<Link
href="/"
className={cn(
'flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors hover:bg-accent',
pathname === '/' && 'bg-accent'
)}
>
<StickyNote className="h-4 w-4" />
Notes
</Link>
<Link
href="/archive"
className={cn(
'flex items-center gap-2 px-6 py-3 text-sm font-medium transition-colors hover:bg-accent',
pathname === '/archive' && 'bg-accent'
)}
>
<Archive className="h-4 w-4" />
Archive
</Link>
</nav>
</header>
)
}