• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jcubic / 10xDevs / 18543522652

15 Oct 2025 09:43PM UTC coverage: 23.472% (+1.5%) from 21.938%
18543522652

push

github

jcubic
Base UI

64 of 98 branches covered (65.31%)

Branch coverage included in aggregate %.

228 of 853 new or added lines in 12 files covered. (26.73%)

2 existing lines in 2 files now uncovered.

439 of 2045 relevant lines covered (21.47%)

2.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.65
/src/components/notes/MainNotesClient.tsx
1
'use client';
1✔
2

NEW
3
import { useEffect, useMemo, useRef, useCallback } from 'react';
×
NEW
4
import { useRouter } from 'next/navigation';
×
5
import type { Note } from '@prisma/client';
NEW
6
import { useNotesContext } from './NotesContext';
×
NEW
7
import { createNote, updateNote, deleteNote } from '@/app/actions/notes';
×
NEW
8
import { extractHeaders } from '@/lib/markdown-headers';
×
NEW
9
import TopNavigationBar from './TopNavigationBar';
×
NEW
10
import LeftPanel from './LeftPanel';
×
NEW
11
import MiddlePanel from './MiddlePanel';
×
NEW
12
import RightPanel from './RightPanel';
×
NEW
13
import styles from './MainNotesLayout.module.css';
×
14

15
interface MainNotesClientProps {
16
  notes: Note[];
17
  initialSelectedNoteId?: number;
18
  lineNumber?: number;
19
}
20

NEW
21
export default function MainNotesClient({
×
NEW
22
  notes,
×
NEW
23
  initialSelectedNoteId,
×
NEW
24
  lineNumber
×
NEW
25
}: MainNotesClientProps) {
×
NEW
26
  const router = useRouter();
×
NEW
27
  const {
×
NEW
28
    selectedNote,
×
NEW
29
    content,
×
NEW
30
    saveStatus,
×
NEW
31
    hasUnsavedChanges,
×
NEW
32
    setSelectedNote,
×
NEW
33
    setContent,
×
NEW
34
    setSaveStatus,
×
NEW
35
    setHasUnsavedChanges
×
NEW
36
  } = useNotesContext();
×
37

NEW
38
  const editorRef = useRef<import('@/types/editor').EditorRef | null>(null);
×
39

40
  // Extract headers from current content
NEW
41
  const headers = useMemo(() => extractHeaders(content), [content]);
×
42

43
  // Initialize with selected note if provided
NEW
44
  useEffect(() => {
×
NEW
45
    if (initialSelectedNoteId && notes.length > 0) {
×
NEW
46
      const note = notes.find((n) => n.id === initialSelectedNoteId);
×
NEW
47
      if (note) {
×
NEW
48
        setSelectedNote(note);
×
NEW
49
        setContent(note.content || '');
×
NEW
50
      }
×
NEW
51
    }
×
NEW
52
  }, [initialSelectedNoteId, notes, setSelectedNote, setContent]);
×
53

NEW
54
  const handleNoteSelect = useCallback(
×
NEW
55
    (noteId: number) => {
×
56
      // Navigate to the note URL which will update the selected note
NEW
57
      router.push(`/note/${noteId}`);
×
NEW
58
    },
×
NEW
59
    [router]
×
NEW
60
  );
×
61

NEW
62
  const handleNewNote = useCallback(async () => {
×
NEW
63
    try {
×
NEW
64
      const newNote = await createNote('New Note');
×
65
      // Navigate to the new note and refresh to show updated notes list
NEW
66
      router.push(`/note/${newNote.id}`);
×
NEW
67
    } catch (error) {
×
NEW
68
      console.error('Failed to create note:', error);
×
NEW
69
    }
×
NEW
70
  }, [router]);
×
71

NEW
72
  const handleContentChange = (newContent: string) => {
×
NEW
73
    setContent(newContent);
×
NEW
74
    setHasUnsavedChanges(true);
×
NEW
75
  };
×
76

NEW
77
  const handleSave = useCallback(async () => {
×
NEW
78
    if (!selectedNote) return;
×
79

NEW
80
    try {
×
NEW
81
      setSaveStatus('saving');
×
NEW
82
      await updateNote(selectedNote.id, { content });
×
NEW
83
      setSaveStatus('saved');
×
NEW
84
      setHasUnsavedChanges(false);
×
85

86
      // Reset status after 2 seconds
NEW
87
      setTimeout(() => setSaveStatus('idle'), 2000);
×
NEW
88
    } catch (error) {
×
NEW
89
      setSaveStatus('error');
×
NEW
90
      console.error('Failed to save note:', error);
×
NEW
91
    }
×
NEW
92
  }, [selectedNote, content, setSaveStatus, setHasUnsavedChanges]);
×
93

NEW
94
  const handleDeleteNote = useCallback(
×
NEW
95
    async (noteId: number) => {
×
NEW
96
      try {
×
NEW
97
        await deleteNote(noteId);
×
98
        // If we deleted the currently selected note, navigate to main notes page
NEW
99
        if (selectedNote?.id === noteId) {
×
NEW
100
          router.push('/');
×
NEW
101
        } else {
×
102
          // Otherwise, refresh to update the notes list
NEW
103
          router.refresh();
×
NEW
104
        }
×
NEW
105
      } catch (error) {
×
NEW
106
        console.error('Failed to delete note:', error);
×
NEW
107
      }
×
NEW
108
    },
×
NEW
109
    [router, selectedNote]
×
NEW
110
  );
×
111

NEW
112
  const handleRenameNote = useCallback(
×
NEW
113
    async (noteId: number, newName: string) => {
×
NEW
114
      try {
×
NEW
115
        const updatedNote = await updateNote(noteId, { name: newName });
×
116

117
        // Update the selected note if it's the one being renamed
NEW
118
        if (selectedNote?.id === noteId) {
×
NEW
119
          setSelectedNote(updatedNote);
×
NEW
120
        }
×
121

122
        // Refresh to update the notes list
NEW
123
        router.refresh();
×
NEW
124
      } catch (error) {
×
NEW
125
        console.error('Failed to rename note:', error);
×
NEW
126
        throw error; // Re-throw to let the component handle the error
×
NEW
127
      }
×
NEW
128
    },
×
NEW
129
    [selectedNote, setSelectedNote, router]
×
NEW
130
  );
×
131

NEW
132
  const handleLogout = () => {
×
133
    // TODO: Implement logout logic
NEW
134
    console.log('Logout clicked');
×
NEW
135
  };
×
136

NEW
137
  const handleHeaderClick = (line: number) => {
×
138
    // Scroll to line in CodeMirror editor
NEW
139
    if (editorRef.current) {
×
NEW
140
      editorRef.current.scrollToLine(line);
×
NEW
141
    }
×
NEW
142
  };
×
143

144
  // Keyboard shortcuts
NEW
145
  useEffect(() => {
×
NEW
146
    const handleKeyDown = (e: KeyboardEvent) => {
×
NEW
147
      if (e.ctrlKey || e.metaKey) {
×
NEW
148
        switch (e.key) {
×
NEW
149
          case 's':
×
NEW
150
            e.preventDefault();
×
NEW
151
            handleSave();
×
NEW
152
            break;
×
NEW
153
          case 'n':
×
NEW
154
            e.preventDefault();
×
NEW
155
            handleNewNote();
×
NEW
156
            break;
×
NEW
157
        }
×
NEW
158
      }
×
NEW
159
    };
×
160

NEW
161
    window.addEventListener('keydown', handleKeyDown);
×
NEW
162
    return () => window.removeEventListener('keydown', handleKeyDown);
×
NEW
163
  }, [selectedNote, content, handleSave, handleNewNote]);
×
164

NEW
165
  return (
×
NEW
166
    <>
×
NEW
167
      <TopNavigationBar hasUnsavedChanges={hasUnsavedChanges} onLogout={handleLogout} />
×
NEW
168
      <div className={styles.panels}>
×
NEW
169
        <LeftPanel
×
NEW
170
          notes={notes}
×
NEW
171
          selectedNoteId={selectedNote?.id || null}
×
NEW
172
          onNoteSelect={handleNoteSelect}
×
NEW
173
          onNewNote={handleNewNote}
×
NEW
174
          onDeleteNote={handleDeleteNote}
×
NEW
175
          onRenameNote={handleRenameNote}
×
NEW
176
        />
×
NEW
177
        <MiddlePanel
×
NEW
178
          note={selectedNote}
×
NEW
179
          content={content}
×
NEW
180
          saveStatus={saveStatus}
×
NEW
181
          onContentChange={handleContentChange}
×
NEW
182
          onSave={handleSave}
×
NEW
183
          onEditorReady={(editor) => (editorRef.current = editor)}
×
NEW
184
        />
×
NEW
185
        <RightPanel
×
NEW
186
          headers={headers}
×
NEW
187
          currentLine={lineNumber}
×
NEW
188
          onHeaderClick={handleHeaderClick}
×
NEW
189
        />
×
NEW
190
      </div>
×
NEW
191
    </>
×
192
  );
NEW
193
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc