'use client' import { useState, useRef, useEffect } from 'react' import { Card } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { Button } from '@/components/ui/button' import { CheckSquare, X, Bell, Image, UserPlus, Palette, Archive, MoreVertical, Undo2, Redo2 } from 'lucide-react' import { createNote } from '@/app/actions/notes' import { CheckItem, NOTE_COLORS, NoteColor } from '@/lib/types' import { Checkbox } from '@/components/ui/checkbox' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '@/components/ui/tooltip' import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { cn } from '@/lib/utils' import { useUndoRedo } from '@/hooks/useUndoRedo' interface NoteState { title: string content: string checkItems: CheckItem[] images: string[] } export function NoteInput() { const [isExpanded, setIsExpanded] = useState(false) const [type, setType] = useState<'text' | 'checklist'>('text') const [isSubmitting, setIsSubmitting] = useState(false) const [color, setColor] = useState('default') const [isArchived, setIsArchived] = useState(false) const fileInputRef = useRef(null) // Undo/Redo state management const { state: noteState, setState: setNoteState, undo, redo, canUndo, canRedo, clear: clearHistory } = useUndoRedo({ title: '', content: '', checkItems: [], images: [] }) const { title, content, checkItems, images } = noteState // Debounced state updates for performance const debounceTimerRef = useRef(null) const updateTitle = (newTitle: string) => { // Clear previous timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current) } // Update immediately for UI setNoteState(prev => ({ ...prev, title: newTitle })) // Debounce history update debounceTimerRef.current = setTimeout(() => { setNoteState(prev => ({ ...prev, title: newTitle })) }, 500) } const updateContent = (newContent: string) => { // Clear previous timer if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current) } // Update immediately for UI setNoteState(prev => ({ ...prev, content: newContent })) // Debounce history update debounceTimerRef.current = setTimeout(() => { setNoteState(prev => ({ ...prev, content: newContent })) }, 500) } const updateCheckItems = (newCheckItems: CheckItem[]) => { setNoteState(prev => ({ ...prev, checkItems: newCheckItems })) } const updateImages = (newImages: string[]) => { setNoteState(prev => ({ ...prev, images: newImages })) } // Cleanup debounce timer useEffect(() => { return () => { if (debounceTimerRef.current) { clearTimeout(debounceTimerRef.current) } } }, []) // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (!isExpanded) return if ((e.ctrlKey || e.metaKey) && e.key === 'z') { e.preventDefault() undo() } if ((e.ctrlKey || e.metaKey) && e.key === 'y') { e.preventDefault() redo() } } window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) }, [isExpanded, undo, redo]) const handleImageUpload = (e: React.ChangeEvent) => { const files = e.target.files if (!files) return // Validate file types const validTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] const maxSize = 5 * 1024 * 1024 // 5MB Array.from(files).forEach(file => { // Validation if (!validTypes.includes(file.type)) { alert(`Invalid file type: ${file.name}. Only JPEG, PNG, GIF, and WebP are allowed.`) return } if (file.size > maxSize) { alert(`File too large: ${file.name}. Maximum size is 5MB.`) return } const reader = new FileReader() reader.onloadend = () => { updateImages([...images, reader.result as string]) } reader.onerror = () => { alert(`Failed to read file: ${file.name}`) } reader.readAsDataURL(file) }) // Reset input e.target.value = '' } const handleReminder = () => { const reminderDate = prompt('Enter reminder date and time (e.g., 2026-01-10 14:30):') if (!reminderDate) return try { const date = new Date(reminderDate) if (isNaN(date.getTime())) { alert('Invalid date format. Please use format: YYYY-MM-DD HH:MM') return } if (date < new Date()) { alert('Reminder date must be in the future') return } // TODO: Store reminder in database and implement notification system alert(`Reminder set for: ${date.toLocaleString()}\n\nNote: Reminder system will be fully implemented in the next update.`) } catch (error) { alert('Failed to set reminder. Please try again.') } } const handleSubmit = async () => { // Validation if (type === 'text' && !content.trim()) { alert('Please enter some content for your note') return } if (type === 'checklist' && checkItems.length === 0) { alert('Please add at least one item to your checklist') return } if (type === 'checklist' && checkItems.every(item => !item.text.trim())) { alert('Checklist items cannot be empty') return } setIsSubmitting(true) try { await createNote({ title: title.trim() || undefined, content: type === 'text' ? content : '', type, checkItems: type === 'checklist' ? checkItems : undefined, color, isArchived, images: images.length > 0 ? images : undefined, }) // Reset form and history setNoteState({ title: '', content: '', checkItems: [], images: [] }) clearHistory() setIsExpanded(false) setType('text') setColor('default') setIsArchived(false) } catch (error) { console.error('Failed to create note:', error) alert('Failed to create note. Please try again.') } finally { setIsSubmitting(false) } } const handleAddCheckItem = () => { updateCheckItems([ ...checkItems, { id: Date.now().toString(), text: '', checked: false }, ]) } const handleUpdateCheckItem = (id: string, text: string) => { updateCheckItems( checkItems.map(item => (item.id === id ? { ...item, text } : item)) ) } const handleRemoveCheckItem = (id: string) => { updateCheckItems(checkItems.filter(item => item.id !== id)) } const handleClose = () => { setIsExpanded(false) setNoteState({ title: '', content: '', checkItems: [], images: [] }) clearHistory() setType('text') setColor('default') setIsArchived(false) } if (!isExpanded) { return (
setIsExpanded(true)} readOnly value="" className="border-0 focus-visible:ring-0 cursor-text" />
) } const colorClasses = NOTE_COLORS[color] || NOTE_COLORS.default return (
updateTitle(e.target.value)} className="border-0 focus-visible:ring-0 text-base font-semibold" /> {/* Image Preview */} {images.length > 0 && (
{images.map((img, idx) => (
{`Upload
))}
)} {type === 'text' ? (