diff --git a/package.json b/package.json index fedd4cd0..6fd33802 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.5", "framer-motion": "^12.10.3", + "jwt-decode": "^4.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router-dom": "^7.6.2", diff --git a/src/components/Form.jsx b/src/components/Form.jsx index beb95e8e..bb0d4342 100644 --- a/src/components/Form.jsx +++ b/src/components/Form.jsx @@ -1,5 +1,8 @@ import React, { useState } from "react"; +const url = "https://api-project-ns11.onrender.com"; +// const url = "http://localhost:8080"; + const Form = ({ setMessages }) => { const [message, setMessage] = useState(""); const [errorMessage, setErrorMessage] = useState(""); @@ -23,17 +26,14 @@ const Form = ({ setMessages }) => { try { const token = localStorage.getItem("token"); // or wherever you store the JWT - const response = await fetch( - "https://api-project-ns11.onrender.com/thoughts", - { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, // include token here - }, - body: JSON.stringify(newMessage), - } - ); + const response = await fetch(`${url}/thoughts`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, // include token here + }, + body: JSON.stringify(newMessage), + }); if (!response.ok) { throw new Error("Failed to post message"); diff --git a/src/components/LikedMessages.jsx b/src/components/LikedMessages.jsx index f76e484b..bdf3dcce 100644 --- a/src/components/LikedMessages.jsx +++ b/src/components/LikedMessages.jsx @@ -16,18 +16,18 @@ const getUserFromToken = () => { } }; -const LikedMessages = (props) => { +const LikedMessages = ({ messages, onUpdateLike }) => { const [likedMessages, setLikedMessages] = useState([]); useEffect(() => { const currentUser = getUserFromToken(); if (!currentUser) return; - const likedMessages = props.messages.filter((message) => + const likedMessages = messages.filter((message) => message.likedBy?.includes(currentUser) ); setLikedMessages(likedMessages); - }, [props.messages]); + }, [messages]); return (
@@ -50,6 +50,7 @@ const LikedMessages = (props) => { message={message.message} time={message.createdAt} likes={message.hearts} + onUpdateLike={onUpdateLike} /> )) ) : ( diff --git a/src/components/Message.jsx b/src/components/Message.jsx index a820d446..7b4ef81f 100644 --- a/src/components/Message.jsx +++ b/src/components/Message.jsx @@ -1,101 +1,100 @@ -import React, { useEffect, useState } from "react"; - -const Message = ({ id, message, time, likes, onDelete, onUpdate }) => { - const thoughtIdUrl = `https://api-project-ns11.onrender.com/thoughts/${id}/like`; +import { jwtDecode } from "jwt-decode"; +import { useEffect, useState } from "react"; + +const url = "https://api-project-ns11.onrender.com"; +// const url = "http://localhost:8080"; + +const Message = ({ + id, + message, + time, + likes, + onDelete, + onUpdate, + onUpdateLike, +}) => { + const thoughtIdUrl = `${url}/thoughts/${id}/like`; const [liked, setLiked] = useState(false); const [likeCount, setLikeCount] = useState(likes); useEffect(() => { - const likedMessages = - JSON.parse(localStorage.getItem("likedMessages")) || []; - if (likedMessages.includes(id)) { - setLiked(true); - } - }, [id]); + const fetchLikedStatus = async () => { + try { + const token = localStorage.getItem("token"); + if (!token) return; - const handleLike = async () => { - try { - const token = localStorage.getItem("token"); + const decoded = jwtDecode(token); + const userId = decoded.id; - let response; - if (liked) { - response = await fetch(thoughtIdUrl, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - if (!response.ok) throw new Error("Failed to unlike"); - - const likedMessages = - JSON.parse(localStorage.getItem("likedMessages")) || []; - const updatedLikedMessages = likedMessages.filter( - (currentId) => currentId !== id - ); - localStorage.setItem( - "likedMessages", - JSON.stringify(updatedLikedMessages) - ); - - setLiked(false); - setLikeCount((count) => count - 1); - } else { - response = await fetch(thoughtIdUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, + const res = await fetch(`${url}/thoughts/liked/${userId}`, { + headers: { Authorization: `Bearer ${token}` }, }); - if (!response.ok) throw new Error("Failed to like"); - const likedMessages = - JSON.parse(localStorage.getItem("likedMessages")) || []; - likedMessages.push(id); - localStorage.setItem("likedMessages", JSON.stringify(likedMessages)); + if (!res.ok) throw new Error("Failed to fetch liked thoughts"); + const data = await res.json(); - setLiked(true); - setLikeCount((count) => count + 1); + const likedIds = data.response.map((t) => t._id); + setLiked(likedIds.includes(id)); // true if this thought is liked + } catch (err) { + console.error(err); } - } catch (error) { - console.error(error); - } - }; + }; - const handleDelete = async () => { + fetchLikedStatus(); + }, [id]); + + const handleLike = async () => { try { - const response = await fetch( - `https://api-project-ns11.onrender.com/thoughts/${id}`, - { - method: "DELETE", - } - ); + const token = localStorage.getItem("token"); + + const response = await fetch(thoughtIdUrl, { + method: liked ? "DELETE" : "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); if (!response.ok) { - const data = await response.json(); - alert("Delete failed: " + data.message); - return; + const errData = await response.json(); + throw new Error(errData.message || "Failed to like/unlike"); } - onDelete(id); + const data = await response.json(); + setLiked(!liked); + setLikeCount(data.thought.hearts); + + // Notify parent to update messages state + onUpdateLike(id, data.thought.likedBy, data.thought.hearts); } catch (error) { - console.error(error); - alert("Delete request failed"); + console.error("Error liking/unliking:", error.message); } }; + const handleDelete = () => { + const confirmDelete = window.confirm( + "Are you sure you want to delete this message?" + ); + if (!confirmDelete) return; + + onDelete(id); + }; + const handleUpdate = async (newMessage) => { + if (!newMessage || newMessage.trim() === "") return; + try { - const response = await fetch( - `https://api-project-ns11.onrender.com/thoughts/${id}`, - { - method: "PATCH", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ message: newMessage }), - } - ); + const token = localStorage.getItem("token"); + + const response = await fetch(`${url}/thoughts/${id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ newMessage }), + }); if (!response.ok) { const data = await response.json(); @@ -160,6 +159,8 @@ const Message = ({ id, message, time, likes, onDelete, onUpdate }) => { diff --git a/src/pages/Main.jsx b/src/pages/Main.jsx index d59721c7..77a5b65a 100644 --- a/src/pages/Main.jsx +++ b/src/pages/Main.jsx @@ -5,6 +5,9 @@ import Form from "../components/Form"; import LikedMessages from "../components/LikedMessages"; import Messages from "../components/Messages"; +const url = "https://api-project-ns11.onrender.com"; +// const url = "http://localhost:8080"; + const Main = () => { const [messages, setMessages] = useState([]); const navigate = useNavigate(); @@ -15,11 +18,12 @@ const Main = () => { navigate("/login"); return; } + + console.log("Hello from the useEffect:", token); + const fetchMessages = async () => { try { - const response = await fetch( - "https://api-project-ns11.onrender.com/thoughts" - ); + const response = await fetch(`${url}/thoughts`); if (!response.ok) { throw new Error("Failed to fetch messages"); } @@ -40,41 +44,49 @@ const Main = () => { try { const token = localStorage.getItem("token"); - const response = await fetch( - `https://api-project-ns11.onrender.com/thoughts/${thoughtId}`, - { - method: "DELETE", - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); + console.log("Token before DELETE:", token); + + const response = await fetch(`${url}/thoughts/${thoughtId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${token}`, + }, + credentials: "include", + }); if (!response.ok) { - throw new Error("Failed to delete message"); + let errorMessage = "Failed to delete message"; + try { + const data = await response.json(); + if (data?.message) errorMessage = data.message; + } catch (err) { + console.warn("No JSON returned from delete response:", err); + } + alert(errorMessage); + return; } setMessages((prev) => prev.filter((msg) => msg._id !== thoughtId)); } catch (error) { console.error("Error deleting message:", error); + alert("Delete request failed"); } }; const handleUpdate = async (thoughtId, newMessage) => { - try { - const token = localStorage.getItem("token"); + const token = localStorage.getItem("token"); + console.log("Token before PATCH:", token); - const response = await fetch( - `https://api-project-ns11.onrender.com/thoughts/${thoughtId}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - body: JSON.stringify({ message: newMessage }), - } - ); + try { + const response = await fetch(`${url}/thoughts/${thoughtId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ message: newMessage }), + credentials: "include", + }); if (!response.ok) { throw new Error("Failed to update message"); @@ -99,6 +111,16 @@ const Main = () => { navigate("/"); }; + const handleUpdateLike = (thoughtId, newLikedBy, newHearts) => { + setMessages((prev) => + prev.map((msg) => + msg._id === thoughtId + ? { ...msg, likedBy: newLikedBy, hearts: newHearts } + : msg + ) + ); + }; + return (
@@ -117,9 +139,10 @@ const Main = () => { messages={messages} onDelete={handleDelete} onUpdate={handleUpdate} + onUpdateLike={handleUpdateLike} /> - +
); diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx index 0d5d825d..f9aa0603 100644 --- a/src/pages/Register.jsx +++ b/src/pages/Register.jsx @@ -1,5 +1,8 @@ import { useState } from "react"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; + +const url = "https://api-project-ns11.onrender.com"; +// const url = "http://localhost:8080"; const Register = () => { const [username, setUsername] = useState(""); @@ -7,21 +10,62 @@ const Register = () => { const [email, setEmail] = useState(""); const [error, setError] = useState(""); + const navigate = useNavigate(); + const handleRegister = async (e) => { e.preventDefault(); + const trimmedUsername = username.trim(); + const trimmedEmail = email.trim(); + const trimmedPassword = password.trim(); + + const usernameRegex = /^[A-Za-z]+$/; + if (!trimmedUsername) { + setError("Username is required."); + return; + } + if (!usernameRegex.test(trimmedUsername)) { + setError("Username can only contain letters."); + return; + } + if (trimmedUsername.length < 3) { + setError("Username must be at least 3 characters long."); + return; + } + + const emailRegex = /^\S+@\S+\.\S+$/; + if (!trimmedEmail) { + setError("Email is required."); + return; + } + if (!emailRegex.test(trimmedEmail)) { + setError("Please enter a valid email address."); + return; + } + + if (!trimmedPassword) { + setError("Password is required."); + return; + } + if (trimmedPassword.length < 6) { + setError("Password must be at least 6 characters long."); + return; + } + try { - const response = await fetch( - "https://api-project-ns11.onrender.com/auth/signup", - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ username, email, password }), - } - ); + const response = await fetch(`${url}/auth/signup`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + username: trimmedUsername, + email: trimmedEmail, + password: trimmedPassword, + }), + }); if (!response.ok) { - throw new Error("Registration failed"); + const data = await response.json(); + throw new Error(data.message || "Registration failed"); } alert("Registration successful!"); @@ -29,6 +73,8 @@ const Register = () => { setEmail(""); setPassword(""); setError(""); + + navigate("/login"); } catch (error) { setError(error.message); }