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

jcubic / 10xDevs / 18569861134

16 Oct 2025 05:39PM UTC coverage: 20.966% (-0.01%) from 20.976%
18569861134

push

github

jcubic
use Peggy parser from SNApp-notes/mobile

67 of 105 branches covered (63.81%)

Branch coverage included in aggregate %.

0 of 22 new or added lines in 3 files covered. (0.0%)

454 of 2380 relevant lines covered (19.08%)

2.38 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

165
  // Unsaved changes warning (US-015)
166
  useEffect(() => {
×
167
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
×
168
      if (hasUnsavedChanges) {
×
169
        e.preventDefault();
×
170
        e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
×
171
        return 'You have unsaved changes. Are you sure you want to leave?';
×
172
      }
×
173
    };
×
174

175
    window.addEventListener('beforeunload', handleBeforeUnload);
×
176
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
×
177
  }, [hasUnsavedChanges]);
×
178

179
  return (
×
180
    <>
×
181
      <TopNavigationBar hasUnsavedChanges={hasUnsavedChanges} onLogout={handleLogout} />
×
182
      <div className={styles.panels}>
×
183
        <LeftPanel
×
184
          notes={notes}
×
185
          selectedNoteId={selectedNote?.id || null}
×
186
          onNoteSelect={handleNoteSelect}
×
187
          onNewNote={handleNewNote}
×
188
          onDeleteNote={handleDeleteNote}
×
189
          onRenameNote={handleRenameNote}
×
190
        />
×
191
        <MiddlePanel
×
192
          note={selectedNote}
×
193
          content={content}
×
194
          saveStatus={saveStatus}
×
195
          onContentChange={handleContentChange}
×
196
          onSave={handleSave}
×
197
          onEditorReady={(editor) => (editorRef.current = editor)}
×
198
        />
×
199
        <RightPanel
×
200
          headers={headers}
×
201
          currentLine={lineNumber}
×
202
          onHeaderClick={handleHeaderClick}
×
203
        />
×
204
      </div>
×
205
    </>
×
206
  );
207
}
×
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