diff --git a/savebook/app/(legal)/privacy/page.js b/savebook/app/(legal)/privacy/page.js
index 0e8dea5..8a8ebf7 100644
--- a/savebook/app/(legal)/privacy/page.js
+++ b/savebook/app/(legal)/privacy/page.js
@@ -397,7 +397,7 @@ const PrivacyPolicyContent = () => {
Policy Updates
- We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last Updated" date. You are advised to review this Privacy Policy periodically for any changes.
+ We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last Updated" date. You are advised to review this Privacy Policy periodically for any changes.
diff --git a/savebook/app/(legal)/terms/page.js b/savebook/app/(legal)/terms/page.js
index 4ada2e8..1ec3bd3 100644
--- a/savebook/app/(legal)/terms/page.js
+++ b/savebook/app/(legal)/terms/page.js
@@ -150,7 +150,7 @@ const TermsOfServiceContent = () => {
- By accessing or using SaveBook ("the Service"), you agree to be bound by these Terms of Service and all applicable laws and regulations. If you do not agree with any of these terms, you are prohibited from using or accessing this Service.
+ By accessing or using SaveBook ("the Service"), you agree to be bound by these Terms of Service and all applicable laws and regulations. If you do not agree with any of these terms, you are prohibited from using or accessing this Service.
- The page you're looking for doesn't exist or has been moved.
+ The page you're looking for doesn't exist or has been moved.
{/* Simple action button */}
diff --git a/savebook/app/page.js b/savebook/app/page.js
index 1463104..3423946 100644
--- a/savebook/app/page.js
+++ b/savebook/app/page.js
@@ -163,7 +163,7 @@ export default function Home() {
className="mt-8 p-4 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-xl"
>
- ✨ You're all set! Start organizing your thoughts and ideas.
+ ✨ You're all set! Start organizing your thoughts and ideas.
)}
diff --git a/savebook/components/notes/AddNote.js b/savebook/components/notes/AddNote.js
index bde454c..029fb0b 100644
--- a/savebook/components/notes/AddNote.js
+++ b/savebook/components/notes/AddNote.js
@@ -1,616 +1,146 @@
-"use client"
-import noteContext from '@/context/noteContext';
-import React, { useContext, useState } from 'react'
-import toast from 'react-hot-toast';
-import Modal from '../common/Modal';
-import AudioRecorder from '@/components/AudioRecorder';
-import AudioPlayer from '@/components/AudioPlayer';
+"use client";
-// Define Note Templates
+import React, { useContext, useState } from "react";
+import noteContext from "@/context/noteContext";
+import toast from "react-hot-toast";
+import { encrypt } from "@/lib/utils/crypto";
+
+/* Note templates */
const NOTE_TEMPLATES = {
- meeting: `Date: [Insert Date]\n\nAttendees: [List attendees]\n\nAgenda:\n- \n- \n- \n\nNotes:\n\nAction Items:\n- \n- `,
- journal: `What happened today:\n[Write your experiences here]\n\nGoals for tomorrow:\n[List your goals]\n\nGratitude:\n[Write things you're grateful for]`,
- checklist: `Project: [Project Name]\n\nTasks:\n- [ ] Define project goal\n- [ ] List all tasks\n- [ ] Assign owners\n- [ ] Set deadlines\n- [ ] Plan resources\n- [ ] Review progress\n- [ ] Complete project`
+ meeting: `Date:\n\nAttendees:\n\nAgenda:\n- \n- \n\nNotes:\n\nAction Items:\n- `,
+ journal: `What happened today:\n\nGoals for tomorrow:\n\nGratitude:\n`,
+ checklist: `Tasks:\n- [ ] Task 1\n- [ ] Task 2\n- [ ] Task 3`,
};
-export default function Addnote() {
- const context = useContext(noteContext);
- const { addNote, notes } = context;
-
- const [note, setNote] = useState({ title: "", description: "", tag: "" });
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- // Modal State
- const [showModal, setShowModal] = useState(false);
- const [pendingTemplate, setPendingTemplate] = useState(null);
- const defaultTags = [
- "General",
- "Basic",
- "Finance",
- "Grocery",
- "Office",
- "Personal",
- "Work",
- "Ideas"
- ];
- const [images, setImages] = useState([]);
- const [preview, setPreview] = useState([]);
-
- // Audio state management
- const [audioBlob, setAudioBlob] = useState(null);
- const [recordedAudioUrl, setRecordedAudioUrl] = useState(null);
- const [audioData, setAudioData] = useState(null);
- const [isUploadingAudio, setIsUploadingAudio] = useState(false);
-
- const handleImageChange = (e) => {
- const files = Array.from(e.target.files);
-
- setImages(files);
- setPreview(files.map(file => URL.createObjectURL(file)));
- };
-
-
- const uploadImages = async () => {
- const formData = new FormData();
- images.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 : [];
- };
-
- // Handle audio recording from AudioRecorder component
- const handleAudioRecorded = (blob) => {
- setAudioBlob(blob);
- // Create temporary URL for preview
- const url = URL.createObjectURL(blob);
- setRecordedAudioUrl(url);
- };
-
- // Upload audio to API
- const uploadAudio = async (blob) => {
- const formData = new FormData();
- formData.append('audio', blob, 'recording.webm');
-
- const res = await fetch('/api/upload/audio', {
- method: 'POST',
- credentials: 'include',
- body: formData,
- });
-
- if (!res.ok) {
- // Log full error response for debugging
- let errorMessage = `HTTP ${res.status}`;
- const contentType = res.headers.get('content-type');
-
- try {
- // Try to parse as JSON first
- if (contentType?.includes('application/json')) {
- const errorData = await res.json();
- errorMessage = errorData.error || errorData.message || errorMessage;
- } else {
- // Fallback to text
- const errorText = await res.text();
- errorMessage = errorText.slice(0, 200) || errorMessage;
- }
- } catch (parseError) {
- // If parsing fails, use status code
- console.error('Failed to parse error response:', parseError);
- }
-
- console.error('Audio upload error:', { status: res.status, error: errorMessage });
- throw new Error(`Audio upload failed: ${errorMessage}`);
- }
-
- const data = await res.json();
- return {
- url: data.audioUrl,
- duration: data.duration || 0,
- };
- };
-
- // Clear audio recording
- const clearAudioRecording = () => {
- if (recordedAudioUrl) {
- URL.revokeObjectURL(recordedAudioUrl);
- }
- setAudioBlob(null);
- setRecordedAudioUrl(null);
- setAudioData(null);
- };
-
-
-
-
-
-
-
- const handleSaveNote = async (e) => {
- e.preventDefault();
- if (isSubmitting) return;
-
- setIsSubmitting(true);
- try {
- // Upload images
- const imageUrls = images.length ? await uploadImages() : [];
-
- // Upload audio if recording exists - REQUIRED before note creation
- let finalAudioData = null;
- if (audioBlob) {
- setIsUploadingAudio(true);
- try {
- finalAudioData = await uploadAudio(audioBlob);
- } catch (audioError) {
- console.error('Audio upload error:', audioError);
- toast.error(audioError.message || 'Audio upload failed. Please try again.');
- // Abort note creation - do NOT save note without audio
- return;
- }
- }
-
- // Save note with audio data (if available)
- await addNote(
- note.title,
- note.description,
- note.tag,
- imageUrls,
- finalAudioData // Pass audio data (or null)
- );
-
- toast.success("Note has been saved");
- setNote({ title: "", description: "", tag: "" });
- setImages([]);
- setPreview([]);
- clearAudioRecording(); // Only clear after successful save
- } catch (error) {
- console.error('Error saving note:', error);
- toast.error(error.message || "Failed to save note. Please try again.");
- } finally {
- setIsSubmitting(false);
- setIsUploadingAudio(false);
- }
- };
-
-
-
-
-
- const onchange = (e) => {
- setNote({ ...note, [e.target.name]: e.target.value });
- }
-
- // Apply Template Handler
- const applyTemplate = (templateKey) => {
- const template = NOTE_TEMPLATES[templateKey];
- if (!template) return;
-
- // If description is empty, directly apply the template
- if (!note.description.trim()) {
- setNote({ ...note, description: template });
- toast.success(`${templateKey.charAt(0).toUpperCase() + templateKey.slice(1)} template applied!`);
- } else {
- // If description has content, trigger modal
- setPendingTemplate({ key: templateKey, content: template });
- setShowModal(true);
- }
- }
-
- const confirmTemplateChange = () => {
- if (pendingTemplate) {
- setNote({ ...note, description: pendingTemplate.content });
- toast.success(`${pendingTemplate.key.charAt(0).toUpperCase() + pendingTemplate.key.slice(1)} template applied!`);
- handleCloseModal();
- }
+export default function AddNote() {
+ const { addNote, notes } = useContext(noteContext);
+
+ const [note, setNote] = useState({ title: "", description: "", tag: "" });
+ const [isSubmitting, setIsSubmitting] = useState(false);
+
+ const defaultTags = [
+ "General",
+ "Basic",
+ "Finance",
+ "Grocery",
+ "Office",
+ "Personal",
+ "Work",
+ "Ideas",
+ ];
+
+ const handleChange = (e) => {
+ setNote({ ...note, [e.target.name]: e.target.value });
+ };
+
+ const handleSaveNote = async (e) => {
+ e.preventDefault();
+ if (isSubmitting) return;
+
+ setIsSubmitting(true);
+
+ try {
+ const secret = localStorage.getItem("encryptionKey");
+ if (!secret) {
+ toast.error("Encryption key missing. Please log in again.");
+ return;
+ }
+
+ const encryptedTitle = encrypt(note.title, secret);
+ const encryptedDescription = encrypt(note.description, secret);
+
+ await addNote(encryptedTitle, encryptedDescription, note.tag);
+
+ toast.success("Note saved");
+ setNote({ title: "", description: "", tag: "" });
+ } catch (err) {
+ toast.error("Failed to save note");
+ } finally {
+ setIsSubmitting(false);
}
+ };
- const handleCloseModal = () => {
- setShowModal(false);
- setPendingTemplate(null);
+ const applyTemplate = (key) => {
+ if (NOTE_TEMPLATES[key]) {
+ setNote({ ...note, description: NOTE_TEMPLATES[key] });
+ toast.success("Template applied");
}
- // Collect unique tags from existing notes
- const userTags = Array.from(
- new Set(
- (Array.isArray(notes) ? notes : [])
- .map(note => note.tag)
- .filter(tag => tag && tag.trim() !== "")
- )
- );
-
- const allTags = Array.from(new Set([...defaultTags, ...userTags]));
- const isFormValid = note.title.length >= 5 && note.description.length >= 5 && note.tag.length >= 2;
-
- return (
-
-
- {/* Header */}
-
-
- Notebook on the Cloud
-
-
- Your notes, securely stored and accessible anywhere
-
-
-
- {/* Add Note Form */}
-
-
- {/* Quick Tips */}
-
-
-
-
-
- Quick Tips
-
-
-
- •
- Use descriptive titles to easily find your notes later
-
-
- •
- Click template buttons (Meeting, Daily, Checklist) to auto-fill note structure
-
-
- •
- Tags help organize and categorize your notes effectively
-
-
- •
- All fields require at least 5 characters for better note quality
-
-
-
-
- {/* Confirmation Modal */}
-
-
- Cancel
-
-
- Confirm Replacement
-
- >
- }
- >
-
-
- Are you sure you want to replace your current note content with the
- {pendingTemplate?.key}
- template?
-
-
- ⚠️ This action cannot be undone. Your current description will be overwritten.
-
-
-
+ };
+
+ const userTags = Array.from(
+ new Set((Array.isArray(notes) ? notes : []).map((n) => n.tag).filter(Boolean))
+ );
+
+ const allTags = Array.from(new Set([...defaultTags, ...userTags]));
+
+ const isFormValid =
+ note.title.length >= 5 &&
+ note.description.length >= 5 &&
+ note.tag.length >= 2;
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/savebook/components/notes/NoteItem.js b/savebook/components/notes/NoteItem.js
index 0469e13..6ed4f60 100644
--- a/savebook/components/notes/NoteItem.js
+++ b/savebook/components/notes/NoteItem.js
@@ -1,320 +1,167 @@
-import noteContext from '@/context/noteContext';
-import React, { useContext, useState, useMemo } from 'react'
-import LinkPreviewCard from './LinkPreviewCard';
-import toast from 'react-hot-toast';
+"use client";
-export default function NoteItem(props) {
- const context = useContext(noteContext);
- const { deleteNote } = context;
- const { note, updateNote } = props;
- const [isDeleting, setIsDeleting] = useState(false);
- const [previewImage, setPreviewImage] = useState(null);
+import React, { useContext, useMemo, useState } from "react";
+import noteContext from "@/context/noteContext";
+import { decrypt } from "@/lib/utils/crypto";
+import toast from "react-hot-toast";
+import LinkPreviewCard from "./LinkPreviewCard";
- const handleDelete = async () => {
- setIsDeleting(true);
- try {
- await deleteNote(note._id);
- toast.success("Note deleted successfully 🗑️");
- } catch (error) {
- toast.error("Failed to delete note");
- } finally {
- setIsDeleting(false);
- }
- }
-
- const handleEdit = () => {
- updateNote(note);
- }
-
- // Enhanced tag colors with better dark mode variants
- const getTagColor = (tag) => {
- const tagColors = {
- 'General': { bg: 'bg-blue-500', text: 'text-blue-100' },
- 'Basic': { bg: 'bg-gray-500', text: 'text-gray-100' },
- 'Finance': { bg: 'bg-green-500', text: 'text-green-100' },
- 'Grocery': { bg: 'bg-orange-500', text: 'text-orange-100' },
- 'Office': { bg: 'bg-purple-500', text: 'text-purple-100' },
- 'Personal': { bg: 'bg-pink-500', text: 'text-pink-100' },
- 'Work': { bg: 'bg-indigo-500', text: 'text-indigo-100' },
- 'Ideas': { bg: 'bg-teal-500', text: 'text-teal-100' }
- };
- return tagColors[tag] || { bg: 'bg-blue-500', text: 'text-blue-100' };
- };
-
- // Format date with relative time -with error handling
- const formatDate = (dateString) => {
- try {
- const date = new Date(dateString);
- if (isNaN(date.getTime())) return 'Unknown date';
-
- const now = new Date();
-
- // Compare calendar dates in LOCAL timezone
- const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
- const noteDay = new Date(date.getFullYear(), date.getMonth(), date.getDate());
+export default function NoteItem({ note, updateNote }) {
+ const { deleteNote } = useContext(noteContext);
- const diffTime = today - noteDay;
- const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [previewImage, setPreviewImage] = useState(null);
- if (diffDays === 0) return 'Today';
- if (diffDays === 1) return 'Yesterday';
- if (diffDays < 7) return `${diffDays} days ago`;
- if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
-
- return date.toLocaleDateString('en-US', {
- month: 'short',
- day: 'numeric',
- year: date.getFullYear() !== now.getFullYear() ? 'numeric' : undefined
- });
- } catch (err) {
- console.error("Date formatting error:", err);
- return 'Unknown date';
- }
- };
+ /* 🔐 Decryption */
+ const secret =
+ typeof window !== "undefined"
+ ? localStorage.getItem("encryptionKey")
+ : null;
+ let decryptedTitle = "🔒 Encrypted";
+ let decryptedDescription = "🔒 Encrypted";
- // Calculate reading time - with error handling
- const getReadingTime = (text) => {
- try {
- if (!text) return '< 1 min';
-
- const wordsPerMinute = 200;
- const wordCount = text.split(/\s+/).length || 0;
- const readingTime = Math.ceil(wordCount / wordsPerMinute);
- return readingTime < 1 ? '< 1 min' : `${readingTime} min read`;
- } catch (error) {
- console.error("Reading time calculation error:", error);
- return '< 1 min';
- }
+ try {
+ if (secret) {
+ decryptedTitle = decrypt(note?.title || "", secret);
+ decryptedDescription = decrypt(note?.description || "", secret);
+ }
+ } catch (err) {
+ console.error("Decryption failed:", err);
+ }
+
+ /* Counts */
+ const descriptionLength =
+ typeof decryptedDescription === "string"
+ ? decryptedDescription.length
+ : 0;
+
+ const wordCount = decryptedDescription
+ ? decryptedDescription.split(/\s+/).filter(Boolean).length
+ : 0;
+
+ /* URLs */
+ const noteUrls = useMemo(() => {
+ if (!decryptedDescription) return [];
+ return decryptedDescription.match(/https?:\/\/[^\s]+/g) || [];
+ }, [decryptedDescription]);
+
+ /* Tag color */
+ const getTagColor = (tag) => {
+ const colors = {
+ General: { bg: "bg-blue-500", text: "text-blue-100" },
+ Work: { bg: "bg-indigo-500", text: "text-indigo-100" },
+ Personal: { bg: "bg-pink-500", text: "text-pink-100" },
+ Finance: { bg: "bg-green-500", text: "text-green-100" },
};
-
- // Safely calculate description length and word count
- const descriptionLength = note?.description?.length || 0;
- const wordCount = note?.description ? note.description.split(/\s+/).filter(Boolean).length : 0;
-
- // Safely get tag color
- const tagColor = getTagColor(note?.tag || 'General');
-
- // Extract URLs from description
- const noteUrls = useMemo(() => {
- if (!note?.description) return [];
- const urlRegex = /(https?:\/\/[^\s]+)/g;
- return note.description.match(urlRegex) || [];
- }, [note?.description]);
-
- return (
- <>
-
-
-
- {/* Header with Gradient */}
-
-
-
- {note?.title || 'Untitled Note'}
-
-
- {/* Tag moved to top right corner */}
-
-
- {note?.tag || 'General'}
-
-
-
-
- {/* Metadata Row */}
-
-
-
-
-
-
{getReadingTime(note?.description)}
-
-
-
-
-
-
{wordCount} words
-
-
-
-
- {/* Body */}
-
-
- {note?.description ? (
- note.description.split(/(https?:\/\/[^\s]+)/g).map((part, i) => (
- part.match(/https?:\/\/[^\s]+/) ? (
- e.stopPropagation()}
- >
- {part}
-
- ) : (
- {part}
- )
- ))
- ) : (
- 'No description provided. Click edit to add content to this note.'
- )}
-
-
- {/* Images */}
- {Array.isArray(note?.images) && note.images.length > 0 && (
-
- {note.images.map((img, index) => (
-
setPreviewImage(img)}
- >
-
- {/* Preview */}
-
-
- ))}
-
- )}
- {/* Link Previews */}
- {noteUrls.map((url, index) => (
-
e.stopPropagation()}>
-
-
- ))}
-
- {/* Audio */}
- {note?.audio && note.audio.url && (
-
-
-
-
-
- Audio Recording
-
-
-
- )}
-
-
- {/* Enhanced Footer */}
-
- {/* Action Buttons with Enhanced Design */}
-
-
-
- Edit
-
-
-
-
- {isDeleting ? (
-
-
-
-
- ) : (
-
-
-
- )}
-
-
- {isDeleting ? 'Deleting...' : 'Delete'}
-
-
-
-
- {/* Enhanced*/}
-
-
-
-
-
-
-
{note?.date ? formatDate(note.date) : 'Unknown'}
-
-
-
- {/* Character Count */}
-
-
-
-
-
-
{descriptionLength.toLocaleString()} chars
-
-
-
-
-
- {/* */}
-
-
-
- {/* Glow */}
-
-
-
- {/* Image Preview */}
- {previewImage && (
-
setPreviewImage(null)}
- >
-
- {/* Close Button */}
-
setPreviewImage(null)}
- className="absolute top-4 right-4 bg-gray-900/80 hover:bg-gray-900 text-white rounded-full p-3 transition-all duration-200 z-10 border border-gray-700"
- title="Close preview"
- >
-
-
-
-
-
- {/* Image */}
-
e.stopPropagation()}
- />
-
-
- )}
- >
- );
+ return colors[tag] || colors.General;
+ };
+
+ const tagColor = getTagColor(note?.tag || "General");
+
+ /* Actions */
+ const handleDelete = async () => {
+ setIsDeleting(true);
+ try {
+ await deleteNote(note._id);
+ toast.success("Note deleted");
+ } catch {
+ toast.error("Failed to delete note");
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ const handleEdit = () => {
+ updateNote({
+ ...note,
+ title: decryptedTitle,
+ description: decryptedDescription,
+ });
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ {decryptedTitle || "Untitled Note"}
+
+
+ {note?.tag || "General"}
+
+
+
+ {/* Body */}
+
+
+ {decryptedDescription ||
+ "No description provided. Click edit to add content."}
+
+
+ {/* Images */}
+ {Array.isArray(note?.images) && note.images.length > 0 && (
+
+ {note.images.map((img, i) => (
+
setPreviewImage(img)}
+ />
+ ))}
+
+ )}
+
+ {/* Link previews */}
+ {noteUrls.map((url, i) => (
+
+ ))}
+
+ {/* Audio */}
+ {note?.audio?.url && (
+
+ )}
+
+
+ {/* Footer */}
+
+
{wordCount} words • {descriptionLength} chars
+
+
+
+ Edit
+
+
+
+ {isDeleting ? "Deleting..." : "Delete"}
+
+
+
+
+ {/* Image Preview */}
+ {previewImage && (
+
setPreviewImage(null)}
+ >
+
+
+ )}
+
+ );
}
\ No newline at end of file
diff --git a/savebook/context/NoteState.js b/savebook/context/NoteState.js
index be5e810..3e3bc59 100644
--- a/savebook/context/NoteState.js
+++ b/savebook/context/NoteState.js
@@ -1,151 +1,132 @@
-"use client"
-import React, { useCallback, useState } from 'react'
-import noteContext from './noteContext'
+"use client";
-const NoteState = (props) => {
- const notesInitial = [];
- const [notes, setNotes] = useState(notesInitial)
+import React, { useCallback, useState } from "react";
+import noteContext from "./noteContext";
+import { encrypt } from "@/lib/utils/crypto";
- // Helper function to handle fetch responses
- const handleResponse = async (response) => {
- const contentType = response.headers.get('content-type');
-
- if (!response.ok) {
- const errorText = await response.text();
- let errorMessage = `HTTP ${response.status}`;
-
- try {
- // Try to parse as JSON first
- const errorJson = JSON.parse(errorText);
- errorMessage = errorJson.message || errorJson.error || errorMessage;
- } catch {
- // If not JSON, check if it's HTML
- if (contentType?.includes('text/html')) {
- errorMessage = `Server returned HTML instead of JSON (${response.status})`;
- } else {
- errorMessage = errorText || errorMessage;
- }
- }
-
- throw new Error(errorMessage);
- }
-
- if (!contentType?.includes('application/json')) {
- throw new Error(`Expected JSON but gots ${contentType}`);
- }
-
- return response.json();
- };
+const NoteState = (props) => {
+ const [notes, setNotes] = useState([]);
- // Fetch all notes with useCallback
+ // Fetch all notes
const getNotes = useCallback(async () => {
try {
- const response = await fetch(`/api/notes`, {
- method: 'GET',
- credentials: "include",
+ const response = await fetch("/api/notes", {
+ method: "GET",
headers: {
- 'Content-Type': 'application/json'
- }
- });
- const parsedText = await response.json();
- setNotes(parsedText)
- } catch (error) {
- console.error('Error fetching notes:', error);
- }
- }, []);
-
- // Add note
- const addNote = useCallback(async (title, description, tag, images = [], audio = null) => {
- try {
- const response = await fetch(`/api/notes`, {
- method: 'POST',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json'
+ "Content-Type": "application/json",
},
- body: JSON.stringify({ title, description, tag, images, audio })
});
- const note = await response.json();
- // Optimistic update - add to existing notes instead of refetching
- setNotes(prevNotes => [note, ...(Array.isArray(prevNotes) ? prevNotes : [])]);
+
+ const data = await response.json();
+ setNotes(data);
} catch (error) {
- console.error('Error adding note:', error);
- // If error, refetch to ensure consistency
- getNotes();
+ console.error("Error fetching notes:", error);
}
- }, [getNotes]);
+ }, []);
- // delete note
- const deleteNote = useCallback(async (id) => {
- try {
- const response = await fetch(`/api/notes/${id}`, {
- method: 'DELETE',
- credentials: "include",
- headers: {
- 'Content-Type': 'application/json'
- }
- });
- await response.json();
+ // Add note (ALREADY ENCRYPTED BEFORE CALLING THIS)
+ const addNote = useCallback(
+ async (title, description, tag) => {
+ try {
+ const response = await fetch("/api/notes", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ title, description, tag }),
+ });
- // Optimistic update
- const newNotes = notes.filter((note) => note._id !== id);
- setNotes(newNotes);
- } catch (error) {
- console.error('Error deleting note:', error);
- getNotes(); // Refetch on error
- }
- }, [notes, getNotes]);
+ const note = await response.json();
+ setNotes((prev) => [note, ...prev]);
+ } catch (error) {
+ console.error("Error adding note:", error);
+ getNotes();
+ }
+ },
+ [getNotes]
+ );
- // Edit note
- const editNote = useCallback(
- async (id, title, description, tag, images = [], audio = null) => {
- try {
- const response = await fetch(`/api/notes/${id}`, {
- method: "PUT",
- credentials: "include",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ title, description, tag, images, audio }),
- });
+ // Delete note
+ const deleteNote = useCallback(
+ async (id) => {
+ try {
+ await fetch(`/api/notes/${id}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ });
- if (!response.ok) {
- throw new Error("Failed to update note");
+ setNotes((prev) => prev.filter((note) => note._id !== id));
+ } catch (error) {
+ console.error("Error deleting note:", error);
+ getNotes();
}
+ },
+ [getNotes]
+ );
- const updatedNote = await response.json();
+ // ✨ EDIT NOTE — FULLY E2EE SAFE ✨
+ const editNote = useCallback(
+ async (id, title, description, tag) => {
+ try {
+ const secret = localStorage.getItem("encryptionKey");
+ if (!secret) {
+ throw new Error("Encryption key missing");
+ }
-
- setNotes((prev) =>
- prev.map((note) =>
- note._id === id ? updatedNote : note
- )
- );
- } catch (error) {
- console.error("Error updating note:", error);
- toast.error("Failed to update note");
- getNotes(); // fallback
- throw error;
- }
- },
- [getNotes]
-);
+ // 🔐 Encrypt BEFORE sending to server
+ const encryptedTitle = encrypt(title, secret);
+ const encryptedDescription = encrypt(description, secret);
+ await fetch(`/api/notes/${id}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ title: encryptedTitle,
+ description: encryptedDescription,
+ tag,
+ }),
+ });
+
+ // Optimistic update (store encrypted data in state)
+ setNotes((prev) =>
+ prev.map((note) =>
+ note._id === id
+ ? {
+ ...note,
+ title: encryptedTitle,
+ description: encryptedDescription,
+ tag,
+ }
+ : note
+ )
+ );
+ } catch (error) {
+ console.error("Error updating note:", error);
+ getNotes();
+ throw error;
+ }
+ },
+ [getNotes]
+ );
return (
-
+
{props.children}
- )
-}
-export default NoteState;
-
+ );
+};
+export default NoteState;