Skip to content

Commit 8bff3f4

Browse files
Merge pull request #222 from sahilrajput0fficial/main
fix: notes lose unsaved changes on refresh (closes #176)
2 parents 3a893e2 + 59a27b5 commit 8bff3f4

2 files changed

Lines changed: 100 additions & 3 deletions

File tree

savebook/components/notes/AddNote.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client"
22
import noteContext from '@/context/noteContext';
3-
import React, { useContext, useState } from 'react'
3+
import React, { useContext, useState, useEffect, useRef } from 'react'
44
import toast from 'react-hot-toast';
55

66
import Modal from '../common/Modal';
@@ -149,6 +149,7 @@ export default function Addnote() {
149149

150150
toast.success("Note has been saved");
151151
setNote({ title: "", description: "", tag: "" });
152+
localStorage.removeItem('savebook_note_draft'); // Clear draft on success
152153
setImages([]);
153154
setPreview([]);
154155
clearAudioRecording();
@@ -191,6 +192,39 @@ export default function Addnote() {
191192
setPendingTemplate(null);
192193
}
193194

195+
const hasRestored = useRef(false);
196+
197+
// Restore draft from localStorage on mount
198+
useEffect(() => {
199+
if (hasRestored.current) return;
200+
201+
const savedDraft = localStorage.getItem('savebook_note_draft');
202+
if (savedDraft) {
203+
try {
204+
const parsedDraft = JSON.parse(savedDraft);
205+
setNote(parsedDraft);
206+
toast.success("Resumed your unsaved draft", {
207+
duration: 3000,
208+
icon: '📝',
209+
});
210+
hasRestored.current = true;
211+
} catch (error) {
212+
console.error("Failed to parse saved draft", error);
213+
localStorage.removeItem('savebook_note_draft');
214+
}
215+
}
216+
}, [setNote]);
217+
218+
useEffect(() => {
219+
if (note.title || (note.description && note.description !== "<p></p>") || note.tag) {
220+
const timeoutId = setTimeout(() => {
221+
localStorage.setItem('savebook_note_draft', JSON.stringify(note));
222+
}, 1000); // 1s debounce
223+
224+
return () => clearTimeout(timeoutId);
225+
}
226+
}, [note]);
227+
194228
const userTags = Array.from(
195229
new Set(
196230
(Array.isArray(notes) ? notes : [])

savebook/components/notes/Notes.js

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ export default function Notes() {
4242
const [previewImage, setPreviewImage] = useState(null);
4343
const [searchTerm, setSearchTerm] = useState('');
4444
const [selectedTag, setSelectedTag] = useState('all');
45+
const [saveStatus, setSaveStatus] = useState('idle'); // 'idle', 'saving', 'saved', 'error'
46+
const [isAutoSaving, setIsAutoSaving] = useState(false);
4547

4648
useEffect(() => {
4749
if (isAuthenticated && !loading) {
@@ -103,7 +105,9 @@ export default function Notes() {
103105
};
104106

105107
const handleClick = async () => {
108+
setIsAutoSaving(false);
106109
try {
110+
setSaveStatus('saving');
107111
let uploadedUrls = [];
108112
if (newImages.length > 0) {
109113
uploadedUrls = await uploadImages(newImages);
@@ -118,6 +122,7 @@ export default function Notes() {
118122
note.etag,
119123
finalImages
120124
);
125+
setSaveStatus('saved');
121126
setIsEditModalOpen(false);
122127
setExistingImages([]);
123128
setNewImages([]);
@@ -127,10 +132,38 @@ export default function Notes() {
127132
toast.success("Note updated successfully! 🎉");
128133
} catch (error) {
129134
console.error(error);
135+
setSaveStatus('error');
130136
toast.error("Failed to update note");
131137
}
132138
};
133139

140+
// Autosave logic for editing
141+
useEffect(() => {
142+
if (!isEditModalOpen || !note.id || isAutoSaving) return;
143+
144+
// Skip saving if content is too short (min length 5)
145+
if (note.etitle.length < 5 || note.edescription.length < 5 || note.etag.length < 2) return;
146+
147+
const timer = setTimeout(async () => {
148+
try {
149+
setSaveStatus('saving');
150+
await editNote(
151+
note.id,
152+
note.etitle,
153+
note.edescription,
154+
note.etag,
155+
existingImages // Just save current images, new ones handled by manual save or upload-on-select
156+
);
157+
setSaveStatus('saved');
158+
} catch (err) {
159+
console.error("Autosave failed", err);
160+
setSaveStatus('error');
161+
}
162+
}, 1000); // 1s debounce
163+
164+
return () => clearTimeout(timer);
165+
}, [note.etitle, note.edescription, note.etag, isEditModalOpen, note.id, editNote, existingImages]);
166+
134167
// Add new images
135168
const handleNewImageChange = (e) => {
136169
const files = Array.from(e.target.files);
@@ -195,7 +228,37 @@ export default function Notes() {
195228
<h2 className="text-2xl font-bold text-white">
196229
Edit Note
197230
</h2>
198-
<p className="text-gray-400 text-sm mt-1">Update your note details</p>
231+
<div className="flex items-center mt-1">
232+
<p className="text-gray-400 text-sm">Update your note details</p>
233+
<span className="mx-2 text-gray-600"></span>
234+
<div className="flex items-center text-xs">
235+
{saveStatus === 'saving' && (
236+
<span className="text-blue-400 flex items-center">
237+
<svg className="animate-spin h-3 w-3 mr-1" viewBox="0 0 24 24">
238+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
239+
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
240+
</svg>
241+
Saving...
242+
</span>
243+
)}
244+
{saveStatus === 'saved' && (
245+
<span className="text-green-400 flex items-center">
246+
<svg className="h-3 w-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
247+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
248+
</svg>
249+
All changes saved
250+
</span>
251+
)}
252+
{saveStatus === 'error' && (
253+
<span className="text-red-400 flex items-center">
254+
<svg className="h-3 w-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
255+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
256+
</svg>
257+
Save failed
258+
</span>
259+
)}
260+
</div>
261+
</div>
199262
</div>
200263
<button
201264
onClick={() => setIsEditModalOpen(false)}
@@ -585,4 +648,4 @@ export default function Notes() {
585648
</div>
586649
</>
587650
)
588-
}
651+
}

0 commit comments

Comments
 (0)