Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 11 additions & 11 deletions src/components/Form.jsx
Original file line number Diff line number Diff line change
@@ -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("");
Expand All @@ -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");
Expand Down
7 changes: 4 additions & 3 deletions src/components/LikedMessages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="mb-20">
Expand All @@ -50,6 +50,7 @@ const LikedMessages = (props) => {
message={message.message}
time={message.createdAt}
likes={message.hearts}
onUpdateLike={onUpdateLike}
/>
))
) : (
Expand Down
151 changes: 76 additions & 75 deletions src/components/Message.jsx
Original file line number Diff line number Diff line change
@@ -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();
Expand Down Expand Up @@ -160,6 +159,8 @@ const Message = ({ id, message, time, likes, onDelete, onUpdate }) => {
<button
onClick={() => {
const newMessage = prompt("Enter new message:", message);
console.log("Updating message to:", newMessage);

if (newMessage && newMessage.trim() !== "") {
handleUpdate(newMessage);
}
Expand Down
3 changes: 2 additions & 1 deletion src/components/Messages.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { motion } from "framer-motion";

import Message from "./Message";

const Messages = ({ messages, onDelete, onUpdate }) => {
const Messages = ({ messages, onDelete, onUpdate, onUpdateLike }) => {
return (
<div className="flex flex-col gap-12 mb-12">
<h2 className="font-sans mb-6 text-2xl text-pink-500">Latest Messages</h2>
Expand All @@ -20,6 +20,7 @@ const Messages = ({ messages, onDelete, onUpdate }) => {
likes={message.hearts}
onDelete={onDelete}
onUpdate={onUpdate}
onUpdateLike={onUpdateLike}
/>
</motion.div>
))}
Expand Down
45 changes: 34 additions & 11 deletions src/pages/Login.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";

const url = "https://api-project-ns11.onrender.com";
// const url = "http://localhost:8080";

const Login = () => {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
Expand All @@ -10,17 +13,36 @@ const Login = () => {

const handleLogin = async (e) => {
e.preventDefault();

const trimmedEmail = email.trim();
const trimmedPassword = password.trim();

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;
}

setError("");
setIsLoading(true);

try {
const response = await fetch(
"https://api-project-ns11.onrender.com/auth/login",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
}
);
const response = await fetch(`${url}/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: trimmedEmail,
password: trimmedPassword,
}),
});

const data = await response.json();

Expand All @@ -29,7 +51,6 @@ const Login = () => {
}

localStorage.setItem("token", data.token);

navigate("/app");
} catch (error) {
setError(error.message);
Expand All @@ -40,7 +61,7 @@ const Login = () => {

return (
<div className="flex items-center justify-center py-10 px-4">
<div className="w-full max-w-[500px] rounded-md bg-white p-8 ">
<div className="w-full max-w-[500px] rounded-md bg-white p-8">
<h2 className="mb-6 text-center font-sans text-4xl font-bold text-pink-500">
Log in
</h2>
Expand All @@ -60,6 +81,7 @@ const Login = () => {
id="email"
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-pink-500 focus:outline-none focus:ring-1 focus:ring-pink-500"
placeholder="[email protected]"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
Expand All @@ -75,12 +97,13 @@ const Login = () => {
id="password"
className="mt-1 w-full rounded-md border border-gray-300 px-3 py-2 shadow-sm focus:border-pink-500 focus:outline-none focus:ring-1 focus:ring-pink-500"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button
type="submit"
className="self-start flex items-center justify-center gap-1 px-3 py-2 border-none rounded-[15px] bg-pink-200 font-bold text-sm cursor-pointer hover:bg-pink-300 transition"
className="self-start flex items-center justify-center gap-1 px-3 py-2 border-none rounded-[15px] bg-pink-200 font-bold text-sm cursor-pointer hover:bg-pink-300 transition"
>
Log In
</button>
Expand Down
Loading