+
+
+ {/* Logo Skeleton */}
+
+
+ {/* Heading Skeleton */}
+
+
+
+ {/* Username Field Skeleton */}
+
+
+ {/* White box to mimic actual username input */}
+
- );
-};
-
-// Main component
-export default function Signup() {
- const { isAuthenticated } = useAuth();
- // Show loading while checking initial auth state
- if (isAuthenticated === undefined) {
- return (
-
-
- {/* Header Skeleton */}
-
+ {/* Password Field Skeleton */}
+
- {/* Form Skeleton */}
-
-
-
-
-
- );
- }
+ {/* Confirm Password Field Skeleton */}
+
- // // Redirect if already authenticated
- // if (isAuthenticated) {
- // return (
- //
- //
- //
- //
Redirecting...
- //
- //
- // );
- // }
+ {/* Global Message Skeleton */}
+
- return (
-
-
- {/* Header */}
-
-
-
- Create Account
-
-
- Join us and get started
-
-
+ {/* Submit Button Skeleton */}
+
- {/* Signup Form */}
-
-
-
-
+ {/* Login Link Skeleton */}
+
- );
-}
\ No newline at end of file
+
+
+ );
+};
\ No newline at end of file
diff --git a/savebook/app/api/auth/login/route.js b/savebook/app/api/auth/login/route.js
index 79f88dc..358b56a 100644
--- a/savebook/app/api/auth/login/route.js
+++ b/savebook/app/api/auth/login/route.js
@@ -3,53 +3,48 @@ import dbConnect from "@/lib/db/mongodb";
import User from "@/lib/models/User";
import { generateAuthToken } from "@/lib/utils/jwtAuth";
import { generateRecoveryCodes } from "@/lib/utils/recoveryCodes";
+import bcrypt from "bcryptjs";
export async function POST(request) {
try {
await dbConnect();
-
const { username, password } = await request.json();
if (
- !username ||
- !password ||
- typeof username !== "string" ||
- typeof password !== "string"
-) {
- return NextResponse.json(
- { success: false, message: "Invalid username or password" },
- { status: 400 }
- );
-}
-
- const user = await User.findOne({ username });
-
-const isPasswordValid = user
- ? await user.comparePassword(password)
- : false;
+ !username ||
+ !password ||
+ typeof username !== "string" ||
+ typeof password !== "string"
+ ) {
+ return NextResponse.json(
+ { success: false, message: "Invalid username or password" },
+ { status: 400 }
+ );
+ }
-if (!user || !isPasswordValid) {
- return NextResponse.json(
- { success: false, message: "Invalid username or password" },
- { status: 401 }
- );
-}
+ // Find user
+ const user = await User.findOne({ username }).select("+password");
+ if (!user) {
+ return NextResponse.json(
+ { success: false, message: "Invalid Username! Try Again!" },
+ { status: 401 }
+ );
+ }
+ // Verify password
+ const isPasswordValid = await bcrypt.compare(password, user.password);
+ if (!isPasswordValid) {
+ return NextResponse.json(
+ { success: false, message: "Invalid username or password" },
+ { status: 401 }
+ );
+ }
- // Generate auth token
+ // Generate token
const { authToken } = await generateAuthToken(user._id.toString());
- // FIRST LOGIN: generate recovery codes
- let recoveryCodes = null;
- if (!user.recoveryCodes || user.recoveryCodes.length === 0) {
- const generated = generateRecoveryCodes(8);
-
- user.recoveryCodes = generated.hashedCodes;
- await user.save();
-
- // Plain codes only sent once
- recoveryCodes = generated.plainCodes;
- }
+ // Generate recovery codes only on first login (optional)
+ const recoveryCodes = generateRecoveryCodes();
const response = NextResponse.json(
{
@@ -63,14 +58,14 @@ if (!user || !isPasswordValid) {
bio: user.bio,
location: user.location,
},
- // only present on first login
- recoveryCodes,
+ recoveryCodes, // only if you want to send them
},
message: "Login successful",
},
{ status: 200 }
);
+ // Set cookie
response.cookies.set("authToken", authToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
@@ -87,4 +82,4 @@ if (!user || !isPasswordValid) {
{ status: 500 }
);
}
-}
+}
\ No newline at end of file
diff --git a/savebook/app/api/auth/register/route.js b/savebook/app/api/auth/register/route.js
index 1dc57da..122de98 100644
--- a/savebook/app/api/auth/register/route.js
+++ b/savebook/app/api/auth/register/route.js
@@ -4,44 +4,55 @@ import dbConnect from "@/lib/db/mongodb";
export async function POST(request) {
try {
- await dbConnect();
-
const { username, password } = await request.json();
-
- // β
Input validation
- if (
- !username ||
- !password ||
- typeof username !== "string" ||
- typeof password !== "string" ||
- password.length < 6
- ) {
+
+ // Validate required fields
+ if (!username || !password) {
return NextResponse.json(
- { success: false, message: "Invalid input" },
+ { success: false, message: "All fields are required" },
{ status: 400 }
);
}
- // β
Prevent username enumeration
- const existingUser = await User.findOne({ username });
+ // Password strength check
+ if (password.length < 6) {
+ return NextResponse.json(
+ { success: false, message: "Password must be at least 6 characters" },
+ { status: 400 }
+ );
+ }
- if (existingUser) {
+ // Check if user exists
+ const user = await User.findOne({ username });
+ if (user) {
return NextResponse.json(
- { success: false, message: "Unable to create account" },
+ { success: false, message: "User with this username already exists" },
{ status: 400 }
);
}
+
+ // Try creating user
+ try {
+ await User.create({ username, password });
- // β
Create user
- await User.create({
- username,
- password
- });
+ return NextResponse.json(
+ { success: true, message: "Account created successfully" },
+ { status: 201 }
+ );
+ } catch (err) {
+ // Handle duplicate key error explicitly
+ if (err.code === 11000) {
+ return NextResponse.json(
+ { success: false, message: "Username already taken" },
+ { status: 400 }
+ );
+ }
- return NextResponse.json(
- { success: true, message: "Account created successfully" },
- { status: 201 }
- );
+ return NextResponse.json(
+ { success: false, message: err.message || "Failed to create user" },
+ { status: 500 }
+ );
+ }
} catch (error) {
console.error("Register error:", error);
return NextResponse.json(
diff --git a/savebook/app/api/auth/update-profile/route.js b/savebook/app/api/auth/update-profile/route.js
index c584cf5..224f3f2 100644
--- a/savebook/app/api/auth/update-profile/route.js
+++ b/savebook/app/api/auth/update-profile/route.js
@@ -11,57 +11,86 @@ export async function PUT(request) {
// Get token from cookies
const authtoken = request.cookies.get("authToken");
-
if (!authtoken) {
- return NextResponse.json({ success: false, message: "Unauthorized - No token provided" }, { status: 401 });
+ return NextResponse.json(
+ { success: false, message: "Unauthorized - No token provided" },
+ { status: 401 }
+ );
}
// Verify token
const decoded = verifyJwtToken(authtoken.value);
-
if (!decoded || !decoded.success) {
- return NextResponse.json({ success: false, message: "Unauthorized - Invalid token" }, { status: 401 });
+ return NextResponse.json(
+ { success: false, message: "Unauthorized - Invalid token" },
+ { status: 401 }
+ );
}
-
// Get user ID from token
const userId = new mongoose.Types.ObjectId(decoded.userId);
- // Get updated user data from request
- const { profileImage, firstName, lastName, bio, location } = await request.json();
- // Update user
+ // Get updated user data from request (only safe fields)
+ const {
+ profileImage,
+ firstName,
+ lastName,
+ bio,
+ location,
+ } = await request.json();
+
+ // Update user with validation
const updatedUser = await User.findByIdAndUpdate(
userId,
- {
+ {
...(profileImage !== undefined && { profileImage }),
...(firstName !== undefined && { firstName }),
...(lastName !== undefined && { lastName }),
...(bio !== undefined && { bio }),
...(location !== undefined && { location })
},
- { new: true, select: "-password" } // Return updated user without password
+ {
+ new: true,
+ select: "-password",
+ runValidators: true // enforce schema validation
+ }
);
if (!updatedUser) {
- return NextResponse.json({ success: false, message: "User not found" }, { status: 404 });
+ return NextResponse.json(
+ { success: false, message: "User not found" },
+ { status: 404 }
+ );
}
- // Return success response with updated user data
- return NextResponse.json({
- success: true,
- message: "Profile updated successfully",
- user: {
- username: updatedUser.username,
- profileImage: updatedUser.profileImage,
- firstName: updatedUser.firstName,
- lastName: updatedUser.lastName,
- bio: updatedUser.bio,
- location: updatedUser.location
- }
- }, { status: 200 });
-
+ // Return success response with updated user data (only safe fields)
+ return NextResponse.json(
+ {
+ success: true,
+ message: "Profile updated successfully",
+ user: {
+ username: updatedUser.username,
+ profileImage: updatedUser.profileImage,
+ firstName: updatedUser.firstName,
+ lastName: updatedUser.lastName,
+ bio: updatedUser.bio,
+ location: updatedUser.location
+ }
+ },
+ { status: 200 }
+ );
} catch (error) {
- console.error("Error updating profile:", error);
- return NextResponse.json({ success: false, message: "Internal Server Error" }, { status: 500 });
+ // Handle validation errors explicitly
+ if (error.name === "ValidationError") {
+ return NextResponse.json(
+ { success: false, message: error.message },
+ { status: 400 }
+ );
+ }
+
+ return NextResponse.json(
+ { success: false, message: "Internal Server Error" },
+ { status: 500 }
+ );
}
}
\ No newline at end of file
diff --git a/savebook/app/api/auth/user/route.js b/savebook/app/api/auth/user/route.js
index 97eca38..92c8ba3 100644
--- a/savebook/app/api/auth/user/route.js
+++ b/savebook/app/api/auth/user/route.js
@@ -1,7 +1,7 @@
import dbConnect from "@/lib/db/mongodb";
import User from "@/lib/models/User";
import { verifyJwtToken } from "@/lib/utils/jwtAuth";
-import { NextResponse } from 'next/server';
+import { NextResponse } from "next/server";
export async function GET(request) {
try {
@@ -12,51 +12,34 @@ export async function GET(request) {
const authToken = request.cookies.get('authToken');
const tokenValue = authToken?.value;
- if (!tokenValue) {
- return NextResponse.json(
- { success: false, message: 'Unauthorized - No token provided' },
- { status: 401 }
- );
- }
-
- // Verify token
- const tokenInfo = await verifyJwtToken(tokenValue);
-
- if (!tokenInfo || !tokenInfo.success) {
- return NextResponse.json(
- { success: false, message: 'Unauthorized - Invalid token' },
- { status: 401 }
- );
- }
-
- // Find user by ID but exclude the password
- const user = await User.findById(tokenInfo.userId).select("-password");
-
- if (!user) {
- return NextResponse.json(
- { success: false, message: "User not found" },
- { status: 404 }
- );
- }
-
- // Return user data
- return NextResponse.json({
- success: true,
- user: {
- username: user.username,
- profileImage: user.profileImage,
- firstName: user.firstName,
- lastName: user.lastName,
- bio: user.bio,
- location: user.location
- }
- }, { status: 200 });
-
- } catch (error) {
- console.error("Error fetching user:", error);
- return NextResponse.json(
- { success: false, message: "Internal Server Error" },
- { status: 500 }
- );
+ await dbConnect();
+
+ // Use tokenInfo.userId instead of tokenInfo.data._id
+ const user = await User.findById(tokenInfo.userId).select('-password');
+
+ if (!user) {
+ return NextResponse.json(
+ { success: false, message: 'User not found' },
+ { status: 404 }
+ );
}
+
+ return NextResponse.json({
+ success: true,
+ user: {
+ username: user.username,
+ profileImage: user.profileImage,
+ firstName: user.firstName,
+ lastName: user.lastName,
+ bio: user.bio,
+ location: user.location
+ }
+ });
+ } catch (error) {
+ console.error("Error fetching user:", error);
+ return NextResponse.json(
+ { success: false, message: "Internal Server Error" },
+ { status: 500 }
+ );
+ }
}
\ No newline at end of file
diff --git a/savebook/app/api/notes/[id]/route.js b/savebook/app/api/notes/[id]/route.js
index e861427..2e66baf 100644
--- a/savebook/app/api/notes/[id]/route.js
+++ b/savebook/app/api/notes/[id]/route.js
@@ -1,7 +1,7 @@
-import { NextResponse } from 'next/server';
-import mongoose from 'mongoose';
-import dbConnect from '@/lib/db/mongodb';
-import Notes from '@/lib/models/Notes';
+import { NextResponse } from "next/server";
+import mongoose from "mongoose";
+import dbConnect from "@/lib/db/mongodb";
+import Notes from "@/lib/models/Notes";
import { verifyJwtToken } from "@/lib/utils/jwtAuth";
// Get a specific note by ID
diff --git a/savebook/app/profile/page.js b/savebook/app/profile/page.js
index d1f4beb..1a1483b 100644
--- a/savebook/app/profile/page.js
+++ b/savebook/app/profile/page.js
@@ -1,125 +1,100 @@
"use client";
-import { useState, useEffect } from 'react';
-import { useAuth } from '@/context/auth/authContext';
-import { useRouter } from 'next/navigation';
+
+import { useState, useEffect } from "react";
+import { useAuth } from "@/context/auth/authContext";
+import { useRouter } from "next/navigation";
export default function ProfilePage() {
const { user, loading, checkUserAuthentication } = useAuth();
const router = useRouter();
+
const [formData, setFormData] = useState({
- profileImage: '',
- firstName: '',
- lastName: '',
- bio: '',
- location: ''
+ profileImage: "",
+ firstName: "",
+ lastName: "",
+ bio: "",
+ location: ""
});
- const [imagePreview, setImagePreview] = useState('');
- const [message, setMessage] = useState('');
- const [error, setError] = useState('');
+
+ const [imagePreview, setImagePreview] = useState("");
+ const [message, setMessage] = useState("");
+ const [error, setError] = useState("");
const [isDataLoaded, setIsDataLoaded] = useState(false);
const [isEditing, setIsEditing] = useState(false);
useEffect(() => {
if (user) {
setFormData({
- profileImage: user.profileImage || '',
- firstName: user.firstName || '',
- lastName: user.lastName || '',
- bio: user.bio || '',
- location: user.location || ''
+ profileImage: user.profileImage || "",
+ firstName: user.firstName || "",
+ lastName: user.lastName || "",
+ bio: user.bio || "",
+ location: user.location || ""
});
- setImagePreview(user.profileImage || '');
+ setImagePreview(user.profileImage || "");
setIsDataLoaded(true);
}
}, [user]);
const handleInputChange = (e) => {
const { name, value } = e.target;
- setFormData(prev => ({
- ...prev,
- [name]: value
- }));
+ setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleImageChange = async (e) => {
const file = e.target.files[0];
-
if (file) {
- // Validate file type
- if (!file.type.match('image.*')) {
- setError('Please select an image file');
+ if (!file.type.match("image.*")) {
+ setError("Please select an image file");
return;
}
-
- // Validate file size (max 5MB)
if (file.size > 5 * 1024 * 1024) {
- setError('File size exceeds 5MB limit');
+ setError("File size exceeds 5MB limit");
return;
}
-
- // Show loading state
- setMessage('Uploading image...');
-
- const formData = new FormData();
- formData.append('image', file);
-
+
+ setMessage("Uploading image...");
+ const fd = new FormData();
+ fd.append("image", file);
+
try {
- const response = await fetch('/api/upload', {
- method: 'POST',
- body: formData,
- credentials: 'include'
+ const response = await fetch("/api/upload", {
+ method: "POST",
+ body: fd,
+ credentials: "include"
});
-
const result = await response.json();
-
if (result.success) {
setImagePreview(result.imageUrl);
- setFormData(prev => ({
- ...prev,
- profileImage: result.imageUrl
- }));
- setMessage('Image uploaded successfully!');
- setError(''); // Clear any previous error
-
- // Clear message after 2 seconds
- setTimeout(() => setMessage(''), 2000);
+ setFormData((prev) => ({ ...prev, profileImage: result.imageUrl }));
+ setMessage("Image uploaded successfully!");
+ setError("");
+ setTimeout(() => setMessage(""), 2000);
} else {
- setError(result.message || 'Failed to upload image');
+ setError(result.message || "Failed to upload image");
}
- } catch (err) {
- setError('An error occurred while uploading the image');
- console.error('Image upload error:', err);
+ } catch {
+ setError("An error occurred while uploading the image");
}
}
};
const handleSubmit = async (e) => {
e.preventDefault();
- setMessage('');
- setError('');
+ setMessage("");
+ setError("");
try {
- const response = await fetch('/api/auth/update-profile', {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- profileImage: formData.profileImage,
- firstName: formData.firstName,
- lastName: formData.lastName,
- bio: formData.bio,
- location: formData.location
- }),
- credentials: 'include'
+ const response = await fetch("/api/auth/update-profile", {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(formData),
+ credentials: "include"
});
-
const data = await response.json();
if (data.success) {
- setMessage('Profile updated successfully!');
-
- // Update form data to reflect the changes immediately
+ setMessage("Profile updated successfully!");
setFormData({
profileImage: data.user.profileImage,
firstName: data.user.firstName,
@@ -127,34 +102,23 @@ export default function ProfilePage() {
bio: data.user.bio,
location: data.user.location
});
-
- // Update image preview
setImagePreview(data.user.profileImage);
-
- // Refresh user data from the server to ensure we have the latest data in context
+
if (checkUserAuthentication) {
await checkUserAuthentication();
}
-
- setTimeout(() => {
- setIsEditing(false);
- }, 500);
-
- setTimeout(() => {
- setMessage(''); // Clear message
- // Optionally redirect after update
- // router.push('/'); // Redirect to home page after successful update
- }, 2000);
+
+ setTimeout(() => setIsEditing(false), 500);
+ setTimeout(() => setMessage(""), 2000);
} else {
- setError(data.message || 'Failed to update profile');
+ setError(data.message || "Failed to update profile");
}
- } catch (err) {
- setError('An error occurred while updating profile');
- console.error('Profile update error:', err);
+ } catch {
+ setError("An error occurred while updating profile");
}
};
- if (loading) {
+ if (loading || !isDataLoaded) {
return (
@@ -163,162 +127,140 @@ export default function ProfilePage() {
}
if (!user) {
- router.push('/login');
+ router.push("/login");
return null;
}
- if (loading || !isDataLoaded) {
- return (
-
- );
- }
-
- const handleEditClick = () => {
- setIsEditing(true);
- };
-
const handleCancelEdit = () => {
setIsEditing(false);
- // Reset form to current user data
if (user) {
setFormData({
- profileImage: user.profileImage || '',
- firstName: user.firstName || '',
- lastName: user.lastName || '',
- bio: user.bio || '',
- location: user.location || ''
+ profileImage: user.profileImage || "",
+ firstName: user.firstName || "",
+ lastName: user.lastName || "",
+ bio: user.bio || "",
+ location: user.location || ""
});
- setImagePreview(user.profileImage || '');
+ setImagePreview(user.profileImage || "");
+ setError("");
+ setMessage("");
}
};
-
+
return (
-
Edit Profile
-
+
+ {isEditing ? "Edit Profile" : "Your Profile"}
+
+
{message && (
-
+
{message}
)}
-
{error && (
-
+
{error}
)}
-
+
{!isEditing ? (
- {/* Profile Preview Card */}
-
-
-
Your Profile
-
+ {/* Avatar + username */}
+
+
+ {user?.profileImage ? (
+

+ ) : (
+
+
+ {user?.username?.charAt(0)?.toUpperCase() || "U"}
+
+
+ )}
+
+
+ {user?.username || "N/A"}
+
+
+
+
+
+
Full Name
+
{user.firstName || "N/A"} {user.lastName || ""}
+
+
+
Location
+
{user.location || "N/A"}
-
-
-
- {user?.profileImage ? (
-

+
Bio
+
{user.bio || "N/A"}
+
+
+
+
+
+
+
+ ) : isDataLoaded ? (
+
- ) : isDataLoaded ? (
-
- ) : (
-
- )}
+ {/* Actions */}
+
+
+
+
+
+ ) : null}
diff --git a/savebook/components/notes/AddNote.js b/savebook/components/notes/AddNote.js
index bde454c..11a86a6 100644
--- a/savebook/components/notes/AddNote.js
+++ b/savebook/components/notes/AddNote.js
@@ -1,6 +1,6 @@
-"use client"
+"use client";
import noteContext from '@/context/noteContext';
-import React, { useContext, useState } from 'react'
+import React, { useContext, useState } from 'react';
import toast from 'react-hot-toast';
import Modal from '../common/Modal';
import AudioRecorder from '@/components/AudioRecorder';
@@ -8,609 +8,374 @@ import AudioPlayer from '@/components/AudioPlayer';
// Define 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: [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`
};
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);
+ 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
+ const handleAudioRecorded = (blob) => {
+ setAudioBlob(blob);
+ const url = URL.createObjectURL(blob);
+ setRecordedAudioUrl(url);
+ };
+
+ 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) {
+ let errorMessage = `HTTP ${res.status}`;
+ const contentType = res.headers.get('content-type');
+ try {
+ if (contentType?.includes('application/json')) {
+ const errorData = await res.json();
+ errorMessage = errorData.error || errorData.message || errorMessage;
+ } else {
+ const errorText = await res.text();
+ errorMessage = errorText.slice(0, 200) || errorMessage;
}
- setAudioBlob(null);
- setRecordedAudioUrl(null);
- setAudioData(null);
- };
-
-
-
+ } catch (parseError) {
+ 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 };
+ };
+ const clearAudioRecording = () => {
+ if (recordedAudioUrl) URL.revokeObjectURL(recordedAudioUrl);
+ setAudioBlob(null);
+ setRecordedAudioUrl(null);
+ setAudioData(null);
+ };
+ const handleSaveNote = async (e) => {
+ e.preventDefault();
+ if (isSubmitting) return;
- const handleSaveNote = async (e) => {
- e.preventDefault();
- if (isSubmitting) return;
+ setIsSubmitting(true);
+ try {
+ const imageUrls = images.length ? await uploadImages() : [];
- setIsSubmitting(true);
+ let finalAudioData = null;
+ if (audioBlob) {
+ setIsUploadingAudio(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);
+ finalAudioData = await uploadAudio(audioBlob);
+ } catch (audioError) {
+ console.error('Audio upload error:', audioError);
+ toast.error(audioError.message || 'Audio upload failed. Please try again.');
+ return;
}
- };
-
-
-
-
-
- const onchange = (e) => {
- setNote({ ...note, [e.target.name]: e.target.value });
+ }
+
+ await addNote(
+ note.title,
+ note.description,
+ note.tag,
+ imageUrls,
+ finalAudioData
+ );
+
+ toast.success("Note has been saved");
+ setNote({ title: "", description: "", tag: "" });
+ setImages([]);
+ setPreview([]);
+ clearAudioRecording();
+ } catch (error) {
+ console.error('Error saving note:', error);
+ toast.error(error.message || "Failed to save note. Please try again.");
+ } finally {
+ setIsSubmitting(false);
+ setIsUploadingAudio(false);
}
-
- // 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);
- }
+ };
+
+ // Corrected handler name
+ const handleChange = (e) => {
+ setNote({ ...note, [e.target.name]: e.target.value });
+ };
+
+ // Template handling
+ const applyTemplate = (templateKey) => {
+ const template = NOTE_TEMPLATES[templateKey];
+ if (!template) return;
+
+ if (!note.description.trim()) {
+ setNote({ ...note, description: template });
+ toast.success(`${templateKey.charAt(0).toUpperCase() + templateKey.slice(1)} template applied!`);
+ } else {
+ 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();
- }
+ const confirmTemplateChange = () => {
+ if (pendingTemplate) {
+ setNote({ ...note, description: pendingTemplate.content });
+ toast.success(`${pendingTemplate.key.charAt(0).toUpperCase() + pendingTemplate.key.slice(1)} template applied!`);
+ handleCloseModal();
}
+ };
+
+ const handleCloseModal = () => {
+ setShowModal(false);
+ setPendingTemplate(null);
+ };
+
+ 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
+
+
- const handleCloseModal = () => {
- setShowModal(false);
- setPendingTemplate(null);
- }
- // 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 */}
-
-
- Add New Note
-
-
-
+ );
+}
\ No newline at end of file
diff --git a/savebook/context/auth/AuthState.js b/savebook/context/auth/AuthState.js
index dcdc85d..a7df7a2 100644
--- a/savebook/context/auth/AuthState.js
+++ b/savebook/context/auth/AuthState.js
@@ -46,39 +46,43 @@ const AuthProvider = ({ children }) => {
};
// Login function
- const login = async (username, password) => {
- try {
- setLoading(true);
- const response = await fetch('/api/auth/login', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ username, password }),
- credentials: 'include' // Important: to store the cookie
- });
+ const login = async (username, password, rememberMe) => {
+ try {
+ setLoading(true);
+ const response = await fetch("/api/auth/login", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ username, password, rememberMe }),
+ credentials: "include", // keep cookie
+ });
- const data = await response.json();
-
- if (data.success) {
- setUser(data.data.user);
- setIsAuthenticated(true);
- return { success: true, message: data.message,recoveryCodes: data.data?.recoveryCodes || null };
- } else {
- return {
- success: false,
- message: data.message || "Login failed"
- };
- }
- } catch (error) {
- console.error("Login error:", error);
- return {
- success: false,
- message: "An error occurred during login"
- };
- } finally {
- setLoading(false);
+ const data = await response.json();
+
+ if (response.ok && data.success) {
+ // Only set authenticated on success
+ setUser(data.data.user);
+ setIsAuthenticated(true);
+ return { success: true, message: data.message };
+ } else {
+ // Do not set isAuthenticated here
+ setIsAuthenticated(false);
+ setUser(null);
+ setIsAuthenticated(false);
+ return {
+ success: false,
+ message: data.message || "Invalid credentials",
+ };
}
+ } catch (error) {
+ console.error("Login error:", error);
+ setIsAuthenticated(false);
+ return {
+ success: false,
+ message: "An error occurred during login",
+ };
+ } finally {
+ setLoading(false);
+ }
};
// Register function
@@ -90,7 +94,7 @@ const AuthProvider = ({ children }) => {
headers: {
'Content-Type': 'application/json'
},
- body: JSON.stringify({ username,password }),
+ body: JSON.stringify({ username,password}),
credentials: 'include'
});
@@ -135,6 +139,19 @@ const AuthProvider = ({ children }) => {
}
};
+ //function created for providing path for updateProfile
+ const updateProfile = async (updates) => {
+ const response = await fetch('/api/auth/updateProfile', {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(updates),
+ credentials: 'include'
+ });
+ const data = await response.json();
+ if (data.success) setUser(data.user);
+ return data;
+ };
+
// Create the context value
const contextValue = {
user,
diff --git a/savebook/docker-compose.yml b/savebook/docker-compose.yml
index 2486613..a3a4c15 100644
--- a/savebook/docker-compose.yml
+++ b/savebook/docker-compose.yml
@@ -53,7 +53,7 @@ services:
networks:
- savebook-network
healthcheck:
- test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/auth/user', res => { if (res.statusCode !== 200) process.exit(1); }).on('error', () => process.exit(1));"]
+ test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:3000/api/auth/user"]
interval: 30s
timeout: 10s
retries: 3
diff --git a/savebook/package-lock.json b/savebook/package-lock.json
index a4cf780..f6521ca 100644
--- a/savebook/package-lock.json
+++ b/savebook/package-lock.json
@@ -13,7 +13,7 @@
"cloudinary": "^2.8.0",
"framer-motion": "^12.24.7",
"jose": "^6.1.3",
- "jsonwebtoken": "^9.0.2",
+ "jsonwebtoken": "^9.0.3",
"lucide-react": "^0.562.0",
"mongoose": "^8.19.1",
"next": "^15.5.7",
@@ -4483,12 +4483,12 @@
}
},
"node_modules/jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"license": "MIT",
"dependencies": {
- "jws": "^3.2.2",
+ "jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
@@ -4521,9 +4521,9 @@
}
},
"node_modules/jwa": {
- "version": "1.4.2",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz",
- "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"license": "MIT",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
@@ -4532,12 +4532,12 @@
}
},
"node_modules/jws": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz",
- "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==",
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"license": "MIT",
"dependencies": {
- "jwa": "^1.4.2",
+ "jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
diff --git a/savebook/package.json b/savebook/package.json
index 66eab19..7114296 100644
--- a/savebook/package.json
+++ b/savebook/package.json
@@ -14,7 +14,7 @@
"cloudinary": "^2.8.0",
"framer-motion": "^12.24.7",
"jose": "^6.1.3",
- "jsonwebtoken": "^9.0.2",
+ "jsonwebtoken": "^9.0.3",
"lucide-react": "^0.562.0",
"mongoose": "^8.19.1",
"next": "^15.5.7",