'use client'; import { useEffect, useState, Suspense } from 'react'; import { Plus, FileText, Link2, GitBranch } from 'lucide-react'; import dynamic from 'next/dynamic'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Badge } from '@/components/ui/badge'; import { DailyNoteButton } from '@/components/notes/daily-note-button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; // Lazy load TipTap editor (~80KB TipTap + extensions) const NoteEditor = dynamic( () => import('@/components/notes/note-editor').then((m) => m.NoteEditor), { ssr: false, loading: () => (
Loading editor...
), } ); // Lazy load react-force-graph-2d (~120KB + three.js) const NoteGraph = dynamic( () => import('@/components/notes/note-graph').then((m) => m.NoteGraph), { ssr: false, loading: () => (
Loading graph...
), } ); interface Note { id: string; title: string; content: string; domain: string; created: string; updated: string; } interface Backlink { id: string; title: string; } export default function NotesPage() { const [notes, setNotes] = useState([]); const [selectedNote, setSelectedNote] = useState(null); const [backlinks, setBacklinks] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetchNotes(); }, []); useEffect(() => { if (selectedNote) { fetchBacklinks(selectedNote.id); } }, [selectedNote]); async function fetchNotes() { try { const response = await fetch('/api/notes?sort=-updated'); if (response.ok) { const data = await response.json(); const notesList = data.items || []; setNotes(notesList); if (notesList.length > 0 && !selectedNote) { setSelectedNote(notesList[0]); } } } catch (error) { console.error('Failed to fetch notes:', error); } finally { setLoading(false); } } async function fetchBacklinks(noteId: string) { try { const response = await fetch(`/api/notes/${noteId}/backlinks`); if (response.ok) { const data = await response.json(); setBacklinks(data.items || []); } } catch (error) { console.error('Failed to fetch backlinks:', error); } } async function createNote() { try { const response = await fetch('/api/notes', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: 'Untitled note', content: '', domain: 'personal', }), }); if (response.ok) { const newNote = await response.json(); setNotes([newNote, ...notes]); setSelectedNote(newNote); } } catch (error) { console.error('Failed to create note:', error); } } function handleDailyNoteReady(raw: Record) { const note = raw as unknown as Note; // If the note already appears in the list, just select it const exists = notes.find((n) => n.id === note.id); if (exists) { setSelectedNote(exists); return; } // Otherwise prepend it and select setNotes([note, ...notes]); setSelectedNote(note); } async function updateNote(noteId: string, updates: Partial) { try { await fetch(`/api/notes/${noteId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }); fetchNotes(); } catch (error) { console.error('Failed to update note:', error); } } async function deleteNote(noteId: string) { if (!confirm('Are you sure you want to delete this note?')) return; try { await fetch(`/api/notes/${noteId}`, { method: 'DELETE' }); const updatedNotes = notes.filter((n) => n.id !== noteId); setNotes(updatedNotes); if (selectedNote?.id === noteId) { setSelectedNote(updatedNotes[0] || null); } } catch (error) { console.error('Failed to delete note:', error); } } if (loading) { return

Loading notes...

; } return (

Notes

Connect ideas to the work they shape.

{/* Notes list */}
{notes.length === 0 ? (

No notes yet

) : (
{notes.map((note) => ( ))}
)}
{/* Note editor */} {selectedNote ? (
setSelectedNote({ ...selectedNote, title: e.target.value, }) } onBlur={() => updateNote(selectedNote.id, { title: selectedNote.title, }) } className="w-full text-xl font-semibold outline-none" placeholder="Note title" />
Loading editor...
} > setSelectedNote({ ...selectedNote, content }) } onBlur={() => updateNote(selectedNote.id, { content: selectedNote.content, }) } />
) : (

Select a note or create a new one

)} {/* Backlinks and graph */}
{backlinks.length === 0 ? (

No backlinks

) : (
{backlinks.map((link) => ( ))}
)}
Loading graph...
} > ); }