diff --git a/savebook/components/notes/Notes.js b/savebook/components/notes/Notes.js index 7484dad..dd4341f 100644 --- a/savebook/components/notes/Notes.js +++ b/savebook/components/notes/Notes.js @@ -1,593 +1,202 @@ -"use client" -import noteContext from '@/context/noteContext'; -import { useRouter } from 'next/navigation'; -import React, { useContext, useEffect, useState, Suspense } from 'react' -import toast from 'react-hot-toast'; -import Addnote from './AddNote'; -import NoteItem from './NoteItem'; -import { useAuth } from '@/context/auth/authContext'; +"use client"; + +import noteContext from "@/context/noteContext"; +import { useRouter } from "next/navigation"; +import React, { useContext, useEffect, useState, Suspense } from "react"; +import toast from "react-hot-toast"; +import Addnote from "./AddNote"; +import NoteItem from "./NoteItem"; +import { useAuth } from "@/context/auth/authContext"; // Separate navigation handler component to use router with Suspense const NavigationHandler = ({ isAuthenticated, loading }) => { - const router = useRouter(); - - useEffect(() => { - // Only redirect if loading is complete and user is not authenticated - if (!loading && !isAuthenticated) { - router.push("/login"); - } - }, [isAuthenticated, loading, router]); - - return null; + const router = useRouter(); + + useEffect(() => { + if (!loading && !isAuthenticated) { + router.push("/login"); + } + }, [isAuthenticated, loading, router]); + + return null; }; export default function Notes() { - const { isAuthenticated, loading } = useAuth(); - const context = useContext(noteContext); - const { notes: contextNotes = [], getNotes, editNote } = context || {}; - - // Ensure notes is always an array - const notes = - isAuthenticated && Array.isArray(contextNotes) - ? contextNotes - : []; - - const [isEditModalOpen, setIsEditModalOpen] = useState(false); - const [note, setNote] = useState({ id: "", etitle: "", edescription: "", etag: "" }); - const [existingImages, setExistingImages] = useState([]); - const [newImages, setNewImages] = useState([]); - const [preview, setPreview] = useState([]); - const [replaceImages, setReplaceImages] = useState(false); - const [previewImage, setPreviewImage] = useState(null); - const [searchTerm, setSearchTerm] = useState(''); - const [selectedTag, setSelectedTag] = useState('all'); - - useEffect(() => { - if (isAuthenticated && !loading) { - getNotes().catch(() => toast.error("Failed to load notes")); - } - }, [isAuthenticated, loading, getNotes]); - - // Enhanced tag options with colors - const tagOptions = [ - { id: 1, value: "General", color: "bg-blue-500" }, - { id: 2, value: "Basic", color: "bg-gray-500" }, - { id: 3, value: "Finance", color: "bg-green-500" }, - { id: 4, value: "Grocery", color: "bg-orange-500" }, - { id: 5, value: "Office", color: "bg-purple-500" }, - { id: 6, value: "Personal", color: "bg-pink-500" }, - { id: 7, value: "Work", color: "bg-indigo-500" }, - { id: 8, value: "Ideas", color: "bg-teal-500" } - ]; - - // Filter notes based on search and tag - const filteredNotes = notes.filter(note => { - const matchesSearch = note.title?.toLowerCase().includes(searchTerm.toLowerCase()) || - note.description?.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesTag = selectedTag === 'all' || note.tag === selectedTag; - return matchesSearch && matchesTag; + const { isAuthenticated, loading } = useAuth(); + const context = useContext(noteContext); + const { notes: contextNotes = [], getNotes, editNote } = context || {}; + + const notes = + isAuthenticated && Array.isArray(contextNotes) ? contextNotes : []; + + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [note, setNote] = useState({ + id: "", + etitle: "", + edescription: "", + etag: "", + }); + + const [existingImages, setExistingImages] = useState([]); + const [newImages, setNewImages] = useState([]); + const [preview, setPreview] = useState([]); + const [replaceImages, setReplaceImages] = useState(false); + const [previewImage, setPreviewImage] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedTag, setSelectedTag] = useState("all"); + + // Fetch notes + useEffect(() => { + if (isAuthenticated && !loading) { + getNotes().catch(() => toast.error("Failed to load notes")); + } + }, [isAuthenticated, loading, getNotes]); + + // ✅ RESTORE UNSAVED DRAFT WHEN EDIT MODAL OPENS + useEffect(() => { + if (!isEditModalOpen || !note.id) return; + + const savedDraft = localStorage.getItem(`note-draft-${note.id}`); + if (savedDraft) { + setNote((prev) => ({ + ...prev, + edescription: savedDraft, + })); + } + }, [isEditModalOpen, note.id]); + + // ✅ AUTOSAVE NOTE DESCRIPTION (DEBOUNCED) + useEffect(() => { + if (!isEditModalOpen || !note.id) return; + + const timer = setTimeout(() => { + localStorage.setItem( + `note-draft-${note.id}`, + note.edescription + ); + }, 800); + + return () => clearTimeout(timer); + }, [note.edescription, note.id, isEditModalOpen]); + + const tagOptions = [ + { id: 1, value: "General", color: "bg-blue-500" }, + { id: 2, value: "Basic", color: "bg-gray-500" }, + { id: 3, value: "Finance", color: "bg-green-500" }, + { id: 4, value: "Grocery", color: "bg-orange-500" }, + { id: 5, value: "Office", color: "bg-purple-500" }, + { id: 6, value: "Personal", color: "bg-pink-500" }, + { id: 7, value: "Work", color: "bg-indigo-500" }, + { id: 8, value: "Ideas", color: "bg-teal-500" }, + ]; + + const filteredNotes = notes.filter((n) => { + const matchesSearch = + n.title?.toLowerCase().includes(searchTerm.toLowerCase()) || + n.description?.toLowerCase().includes(searchTerm.toLowerCase()); + + const matchesTag = selectedTag === "all" || n.tag === selectedTag; + return matchesSearch && matchesTag; + }); + + const updateNote = (currentNote) => { + setNote({ + id: currentNote._id, + etitle: currentNote.title, + edescription: currentNote.description, + etag: currentNote.tag, }); - const updateNote = (currentNote) => { - setNote({ - id: currentNote._id, - etitle: currentNote.title, - edescription: currentNote.description, - etag: currentNote.tag - }); - setExistingImages(currentNote.images || []); - setNewImages([]); - setPreview([]); - setReplaceImages(false); - setPreviewImage(null); - setIsEditModalOpen(true); + setExistingImages(currentNote.images || []); + setNewImages([]); + setPreview([]); + setReplaceImages(false); + setPreviewImage(null); + setIsEditModalOpen(true); + }; + + const handleClick = async () => { + try { + await editNote( + note.id, + note.etitle, + note.edescription, + note.etag, + existingImages + ); + + // ✅ clear draft after successful save + localStorage.removeItem(`note-draft-${note.id}`); + + setIsEditModalOpen(false); + toast.success("Note updated successfully!"); + } catch (error) { + console.error(error); + toast.error("Failed to update note"); } + }; - //Upload images to Cloudinary - const uploadImages = async (files) => { - if (!files || files.length === 0) return []; - const formData = new FormData(); - files.forEach(file => formData.append("image", file)); - const res = await fetch("/api/upload/user-media", { - method: "POST", - credentials: "include", - body: formData, - }); - if (!res.ok) { - throw new Error("Image upload failed"); - } - const data = await res.json(); - return Array.isArray(data.imageUrls) ? data.imageUrls : []; - }; - - const handleClick = async () => { - try { - let uploadedUrls = []; - if (newImages.length > 0) { - uploadedUrls = await uploadImages(newImages); - } - const finalImages = replaceImages - ? uploadedUrls - : [...existingImages, ...uploadedUrls]; - await editNote( - note.id, - note.etitle, - note.edescription, - note.etag, - finalImages - ); - setIsEditModalOpen(false); - setExistingImages([]); - setNewImages([]); - setPreview([]); - setReplaceImages(false); - setPreviewImage(null); - toast.success("Note updated successfully! 🎉"); - } catch (error) { - console.error(error); - toast.error("Failed to update note"); - } - }; - - // Add new images - const handleNewImageChange = (e) => { - const files = Array.from(e.target.files); - setNewImages(files); - setPreview(files.map(file => URL.createObjectURL(file))); - }; - - // Remove old image - const removeExistingImage = (index) => { - setExistingImages(prev => prev.filter((_, i) => i !== index)); - }; - - // Remove newly selected image - const removePreviewImage = (index) => { - setNewImages(prev => prev.filter((_, i) => i !== index)); - setPreview(prev => prev.filter((_, i) => i !== index)); - }; - - const onchange = (e) => { - setNote({ ...note, [e.target.name]: e.target.value }); - } + const onchange = (e) => { + setNote({ ...note, [e.target.name]: e.target.value }); + }; - const isFormValid = note.etitle?.length >= 5 && note.edescription?.length >= 5 && note.etag?.length >= 5; - - // Show loading spinner while checking authentication - if (loading) { - return ( -
-
-
-

Loading...

-
-
- ); - } + const isFormValid = + note.etitle.length >= 5 && + note.edescription.length >= 5 && + note.etag.length >= 3; - // Don't render notes if not authenticated (will be redirected) - if (!isAuthenticated) { - return ( - - - - ); - } + if (loading) { + return
Loading...
; + } + if (!isAuthenticated) { return ( - <> - {/* Navigation handler with Suspense */} - - - - - - - {/* Edit Note Modal */} - {isEditModalOpen && ( -
-
- {/* Modal Header */} -
-
-

- Edit Note -

-

Update your note details

-
- -
- - {/* Modal Body */} -
- {/* Title Field */} -
- - -

- {note.etitle.length}/5 characters -

-
- - {/* Description Field */} -
- -