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

jcubic / 10xDevs / 18574558796

16 Oct 2025 08:56PM UTC coverage: 21.131% (+0.2%) from 20.966%
18574558796

push

github

jcubic
add confirmation dialog

59 of 85 branches covered (69.41%)

Branch coverage included in aggregate %.

423 of 2196 relevant lines covered (19.26%)

2.41 hits per line

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

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

3
import { useEffect, useMemo, useRef, useCallback } from 'react';
×
4
import type { NoteTreeNode } from '@/types/tree';
5
import { useNotesContext } from './NotesContext';
×
6
import { createNote, updateNote, deleteNote } from '@/app/actions/notes';
×
7
import { extractHeaders } from '@/lib/parser/markdown-parser';
×
8
import TopNavigationBar from './TopNavigationBar';
×
9
import LeftPanel from './LeftPanel';
×
10
import MiddlePanel from './MiddlePanel';
×
11
import RightPanel from './RightPanel';
×
12
import Footer from '@/components/Footer';
×
13

14
import styles from './MainNotesLayout.module.css';
×
15

16
interface MainNotesClientProps {
17
  lineNumber?: number;
18
}
19

20
export default function MainNotesClient({ lineNumber }: MainNotesClientProps) {
×
21
  const {
×
22
    notes,
×
23
    selectedNoteId,
×
24
    saveStatus,
×
25
    setSelectedNoteId,
×
26
    setSaveStatus,
×
27
    updateNoteContent,
×
28
    updateNoteName,
×
29
    markNoteDirty,
×
30
    getSelectedNote,
×
31
    setNotes
×
32
  } = useNotesContext();
×
33

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

36
  const selectedNote = getSelectedNote();
×
37
  const content = selectedNote?.data?.content || '';
×
38
  const hasUnsavedChanges = selectedNote?.data?.dirty || false;
×
39

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

43
  const handleNoteSelect = useCallback(
×
44
    (noteId: number) => {
×
45
      // Client-side selection only - no router navigation
46
      setSelectedNoteId(noteId);
×
47
    },
×
48
    [setSelectedNoteId]
×
49
  );
×
50

51
  const handleNewNote = useCallback(async () => {
×
52
    try {
×
53
      const newNote = await createNote('New Note');
×
54

55
      // Add the new note to our local state
56
      const newTreeNode: NoteTreeNode = {
×
57
        id: newNote.id,
×
58
        name: newNote.name,
×
59
        data: {
×
60
          content: newNote.content || '',
×
61
          dirty: false
×
62
        }
×
63
      };
×
64

65
      setNotes((prevNotes: NoteTreeNode[]) => [newTreeNode, ...prevNotes]);
×
66
      setSelectedNoteId(newNote.id);
×
67
    } catch (error) {
×
68
      console.error('Failed to create note:', error);
×
69
    }
×
70
  }, [setNotes, setSelectedNoteId]);
×
71

72
  const handleContentChange = (newContent: string) => {
×
73
    if (selectedNoteId) {
×
74
      updateNoteContent(selectedNoteId, newContent);
×
75
    }
×
76
  };
×
77

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

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

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

95
  const handleRefresh = useCallback(async () => {
×
96
    if (!selectedNote) return;
×
97

98
    try {
×
99
      setSaveStatus('saving');
×
100
      const freshNote = await updateNote(selectedNote.id, {}); // Fetch without changes
×
101
      updateNoteContent(selectedNote.id, freshNote.content || '');
×
102
      markNoteDirty(selectedNote.id, false);
×
103
      setSaveStatus('idle');
×
104
    } catch (error) {
×
105
      setSaveStatus('error');
×
106
      console.error('Failed to refresh note:', error);
×
107
    }
×
108
  }, [selectedNote, setSaveStatus, updateNoteContent, markNoteDirty]);
×
109

110
  const handleDeleteNote = useCallback(
×
111
    async (noteId: number) => {
×
112
      try {
×
113
        await deleteNote(noteId);
×
114

115
        // Remove from local state
116
        setNotes((prevNotes: NoteTreeNode[]) =>
×
117
          prevNotes.filter((note: NoteTreeNode) => note.id !== noteId)
×
118
        );
×
119

120
        // If we deleted the currently selected note, clear selection
121
        if (selectedNoteId === noteId) {
×
122
          setSelectedNoteId(null);
×
123
        }
×
124
      } catch (error) {
×
125
        console.error('Failed to delete note:', error);
×
126
      }
×
127
    },
×
128
    [setNotes, selectedNoteId, setSelectedNoteId]
×
129
  );
×
130

131
  const handleRenameNote = useCallback(
×
132
    async (noteId: number, newName: string) => {
×
133
      try {
×
134
        const updatedNote = await updateNote(noteId, { name: newName });
×
135
        updateNoteName(noteId, updatedNote.name);
×
136
      } catch (error) {
×
137
        console.error('Failed to rename note:', error);
×
138
        throw error; // Re-throw to let the component handle the error
×
139
      }
×
140
    },
×
141
    [updateNoteName]
×
142
  );
×
143

144
  const handleLogout = () => {
×
145
    // TODO: Implement logout logic
146
    console.log('Logout clicked');
×
147
  };
×
148

149
  const handleHeaderClick = (line: number) => {
×
150
    // Scroll to line in CodeMirror editor
151
    if (editorRef.current) {
×
152
      editorRef.current.scrollToLine(line);
×
153
    }
×
154
  };
×
155

156
  // Keyboard shortcuts
157
  useEffect(() => {
×
158
    const handleKeyDown = (e: KeyboardEvent) => {
×
159
      if (e.ctrlKey || e.metaKey) {
×
160
        switch (e.key) {
×
161
          case 's':
×
162
            e.preventDefault();
×
163
            handleSave();
×
164
            break;
×
165
          case 'n':
×
166
            e.preventDefault();
×
167
            handleNewNote();
×
168
            break;
×
169
          case 'r':
×
170
            // Only trigger refresh when focused in editor
171
            if (document.activeElement?.closest('[data-editor]')) {
×
172
              e.preventDefault();
×
173
              handleRefresh();
×
174
            }
×
175
            break;
×
176
        }
×
177
      }
×
178
    };
×
179

180
    window.addEventListener('keydown', handleKeyDown);
×
181
    return () => window.removeEventListener('keydown', handleKeyDown);
×
182
  }, [handleSave, handleNewNote, handleRefresh]);
×
183

184
  // Unsaved changes warning (US-015)
185
  useEffect(() => {
×
186
    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
×
187
      if (hasUnsavedChanges) {
×
188
        e.preventDefault();
×
189
        e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
×
190
        return 'You have unsaved changes. Are you sure you want to leave?';
×
191
      }
×
192
    };
×
193

194
    window.addEventListener('beforeunload', handleBeforeUnload);
×
195
    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
×
196
  }, [hasUnsavedChanges]);
×
197

198
  return (
×
199
    <>
×
200
      <TopNavigationBar hasUnsavedChanges={hasUnsavedChanges} onLogout={handleLogout} />
×
201
      <div className={styles.panels}>
×
202
        <LeftPanel
×
203
          notes={notes}
×
204
          selectedNoteId={selectedNoteId}
×
205
          onNoteSelect={handleNoteSelect}
×
206
          onNewNote={handleNewNote}
×
207
          onDeleteNote={handleDeleteNote}
×
208
          onRenameNote={handleRenameNote}
×
209
        />
×
210
        <MiddlePanel
×
211
          note={selectedNote}
×
212
          content={content}
×
213
          saveStatus={saveStatus}
×
214
          onContentChange={handleContentChange}
×
215
          onSave={handleSave}
×
216
          onEditorReady={(editor) => (editorRef.current = editor)}
×
217
        />
×
218
        <RightPanel
×
219
          headers={headers}
×
220
          currentLine={lineNumber}
×
221
          onHeaderClick={handleHeaderClick}
×
222
        />
×
223
      </div>
×
224
      <Footer />
×
225

226
      {/* TODO: Add confirmation dialog back when needed */}
227
    </>
×
228
  );
229
}
×
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