From 78dd352346c8b914d70a93884aee9fd3c76a27da Mon Sep 17 00:00:00 2001 From: Ikki Date: Fri, 8 Nov 2024 14:08:02 +0530 Subject: [PATCH 1/6] profile-page --- frontend/src/App.jsx | 2 + frontend/src/Components/Testimonial.jsx | 6 +- frontend/src/Pages/Profile.jsx | 211 ++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 frontend/src/Pages/Profile.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index efd06a3b..c9b3992c 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,6 +22,7 @@ import NotFound from "./Pages/NotFound.jsx"; import ResetPassword from "./Pages/ResetPassword.jsx"; import ForgotPassword from "./Pages/ForgotPassword.jsx"; import RecipeSuggestions from "./Pages/RecipeSuggestions.jsx"; +import UserProfile from "./Pages/Profile.jsx"; function App() { const [showScroll, setShowScroll] = useState(false); @@ -80,6 +81,7 @@ function App() { element={} /> } /> + } /> } /> } /> } /> diff --git a/frontend/src/Components/Testimonial.jsx b/frontend/src/Components/Testimonial.jsx index 620fa69f..80e0043c 100644 --- a/frontend/src/Components/Testimonial.jsx +++ b/frontend/src/Components/Testimonial.jsx @@ -1,14 +1,16 @@ import React, { useState, useEffect, useCallback } from 'react'; import { ChevronLeft, ChevronRight, Star } from 'lucide-react'; import { useMediaQuery } from 'react-responsive'; - +import { Link } from 'react-router-dom'; const ReviewCard = ({ review }) => (
-
+
{console.log(review)}
{review.userId.firstName}
+

{review.userId.firstName} {review.userId.lastName}

+

{review.role}

diff --git a/frontend/src/Pages/Profile.jsx b/frontend/src/Pages/Profile.jsx new file mode 100644 index 00000000..b930e4b4 --- /dev/null +++ b/frontend/src/Pages/Profile.jsx @@ -0,0 +1,211 @@ +import { useState, useEffect } from 'react' +import { useParams } from 'react-router-dom' +import axios from 'axios' + +export default function UserProfile() { + const { id } = useParams() // Get the user ID from the URL params + const backendURL = import.meta.env.VITE_BACKEND_URL + const token = JSON.parse(localStorage.getItem("tastytoken")) // Token still comes from local storage for authentication + + const [username, setUsername] = useState("ChefJulia") + const [bio, setBio] = useState("Passionate about creating delicious, healthy recipes that anyone can make!") + const [imagePreview, setImagePreview] = useState("/placeholder.svg?height=128&width=128") + const [recipes, setRecipes] = useState([]) + const [likedRecipes, setLikedRecipes] = useState([]) // Added state for liked recipes + const [following, setFollowing] = useState(false) + const [followingCount, setFollowingCount] = useState(false) + const [followers, setFollowers] = useState(0) + const [activeTab, setActiveTab] = useState('recipes') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + // Fetch user information + const fetchUserImage = () => { + axios + .post(`${backendURL}/api/user/fetch`, { id }, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + setUsername(res.data.username) + setBio(res.data.bio) + setImagePreview(res.data.profile) + setFollowingCount(res.data.following.length) + setFollowers(res.data.followers.length) + }) + .catch((err) => { + console.error("Error fetching user data", err) + }) + } + + // Fetch recipes + const fetchRecipes = () => { + setLoading(true) + setError(null) + axios + .post(`${backendURL}/api/recipe/readall`, { id: id }, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + setRecipes(res.data.recipes) + }) + .catch((err) => { + console.error("Error fetching recipes:", err) + setError("Failed to fetch recipes. Please try again.") + }) + .finally(() => setLoading(false)) + } + + // Fetch liked recipes + const fetchLikedRecipes = () => { + axios + .post( + `${backendURL}/api/recipe/liked_recipes`, + { userId: id }, // Use the user ID from URL params + { + headers: { Authorization: `Bearer ${token}` }, + } + ) + .then((res) => { + setLikedRecipes(res.data.likedRecipes) + }) + .catch((err) => { + console.error("Error fetching liked recipes:", err) + setError("Failed to fetch liked recipes. Please try again.") + }) + } + + fetchUserImage() + fetchRecipes() + fetchLikedRecipes() // Call the fetchLikedRecipes function to get liked recipes + }, [id, backendURL, token]) // Use id, backendURL, and token in dependencies + + const handleFollow = () => { + setFollowing(!following) + setFollowers(followers + (following ? -1 : 1)) + } + + return ( +
+ {/* Banner and Profile Picture */} +
+
+ {username} +
+
+ + {/* User Info */} +
+
+
+

{username}

+

{bio}

+
+ +
+ + {/* User Stats */} +
+ {recipes.length} Recipes + {followers} Followers + {followingCount} Following +
+
+ + {/* Tabs for Recipes and Liked Recipes */} +
+
+ + +
+ + {activeTab === 'recipes' && ( +
+ {loading ? ( +

Loading recipes...

+ ) : error ? ( +

{error}

+ ) : recipes.length > 0 ? ( + recipes.map((recipe) => ( +
+ {recipe.name} +
+

{recipe.name}

+

{recipe.description}

+
+ ⭐ {recipe.rating} + 👍 {recipe.likes} +
+
+
+ )) + ) : ( +

No recipes posted

+ )} +
+ )} + + {activeTab === 'liked' && ( +
+ {loading ? ( +

Loading liked recipes...

+ ) : error ? ( +

{error}

+ ) : likedRecipes.length > 0 ? ( + likedRecipes.map((recipe) => ( +
+ {recipe.name} +
+

{recipe.name}

+

{recipe.description}

+
+ ⭐ {recipe.rating} + 👍 {recipe.likes} +
+
+
+ )) + ) : ( +

No liked recipes

+ )} +
+ )} +
+
+ ) +} From 69cac0d0ba29ad9faa5f3ab27c92648d35c4fac5 Mon Sep 17 00:00:00 2001 From: Ikki Date: Fri, 8 Nov 2024 14:14:35 +0530 Subject: [PATCH 2/6] removed-logs --- frontend/src/Components/Testimonial.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Components/Testimonial.jsx b/frontend/src/Components/Testimonial.jsx index 80e0043c..79c9b2b4 100644 --- a/frontend/src/Components/Testimonial.jsx +++ b/frontend/src/Components/Testimonial.jsx @@ -4,7 +4,7 @@ import { useMediaQuery } from 'react-responsive'; import { Link } from 'react-router-dom'; const ReviewCard = ({ review }) => (
-
{console.log(review)} +
{review.userId.firstName}
From 2edca115a6ca38780135298fb78aa35f3a2d754a Mon Sep 17 00:00:00 2001 From: Ikki Date: Sat, 9 Nov 2024 17:57:30 +0530 Subject: [PATCH 3/6] follow-feature-update --- backend/Controllers/UserController.js | 59 +++++++++++++++++++++++++++ backend/routes/web.js | 1 + frontend/src/Pages/Dashboard.jsx | 34 +++++++++++++-- frontend/src/Pages/Profile.jsx | 48 ++++++++++++++++++---- 4 files changed, 131 insertions(+), 11 deletions(-) diff --git a/backend/Controllers/UserController.js b/backend/Controllers/UserController.js index 82a8aaae..e151d084 100644 --- a/backend/Controllers/UserController.js +++ b/backend/Controllers/UserController.js @@ -353,10 +353,69 @@ const deleteUserById = async (req, res) => { }; +const toggleFollowUser = async (req, res) => { + const { userId, username: followerUsername } = req.body; + try { + // Check if the target user exists + const targetUser = await User.findById(userId); + if (!targetUser) { + return res.status(404).json({ message: "User not found" }); + } + + // Find the logged-in user by their username + const loggedInUser = await User.findOne({ username: followerUsername }); + + if (!loggedInUser) { + return res.status(404).json({ message: "Logged-in user not found" }); + } + + // Check if already following + const isFollowing = targetUser.followers.includes(loggedInUser._id); + + if (isFollowing) { + // Unfollow: Remove the target user from logged-in user's following and vice versa + targetUser.followers = targetUser.followers.filter( + (followerId) => followerId.toString() !== loggedInUser._id.toString() + ); + loggedInUser.following = loggedInUser.following.filter( + (followingId) => followingId.toString() !== targetUser._id.toString() + ); + await targetUser.save(); + await loggedInUser.save(); + + return res.status(200).json({ + message: `Successfully unfollowed ${targetUser.username}`, + username: targetUser.username, + followingCount: loggedInUser.following.length, + followersCount: targetUser.followers.length, + }); + } else { + // Follow: Add the target user to logged-in user's following and vice versa + targetUser.followers.push(loggedInUser._id); + loggedInUser.following.push(targetUser._id); + await targetUser.save(); + await loggedInUser.save(); + + return res.status(200).json({ + message: `Successfully followed ${targetUser.username}`, + username: targetUser.username, + followingCount: loggedInUser.following.length, + followersCount: targetUser.followers.length, + }); + } + } catch (error) { + console.error("Error toggling follow status:", error); + res.status(500).json({ message: "Internal server error" }); + } +}; + + + const UserController = { Signup, Login, getAllUserName, + toggleFollowUser, deleteUserById, verifyUserByToken, forgotPassword, diff --git a/backend/routes/web.js b/backend/routes/web.js index 28d5a666..b0182c62 100644 --- a/backend/routes/web.js +++ b/backend/routes/web.js @@ -16,6 +16,7 @@ router.get("/recipe/getcomments/:recipeId", RecipeController.getComments); router.post("/signup", UserController.Signup); router.post("/login", UserController.Login); router.post("/submitFeedback", UserController.submitFeedback); +router.post("/follow", UserController.toggleFollowUser); router.get('/feedback', UserController.getAllFeedback); router.get('/feedback/:id', UserController.getFeedbackByUserId); router.post("/feedback/delete", UserController.deleteFeedbackById); diff --git a/frontend/src/Pages/Dashboard.jsx b/frontend/src/Pages/Dashboard.jsx index 0149e810..b011b0e9 100644 --- a/frontend/src/Pages/Dashboard.jsx +++ b/frontend/src/Pages/Dashboard.jsx @@ -13,6 +13,8 @@ const Dashboard = () => { const backendURL = import.meta.env.VITE_BACKEND_URL; const token = JSON.parse(localStorage.getItem("tastytoken")); const user = useLocation().state.user; + const [userData, setUserData] = useState(null) + const path = useLocation().pathname; const [loading, setLoading] = useState(true); const [isModalOpen, setModalOpen] = useState(false); const [recipes, setRecipes] = useState([]); @@ -29,7 +31,28 @@ const Dashboard = () => { }; const [viewingState, setViewingState] = useState(ViewState.RECIPE); const inputFile = useRef(null); // for redirecting click to open input file - + useEffect(() => { + let token = localStorage.getItem("tastytoken"); + if (token) { + token = JSON.parse(token); + axios + .get(`${backendURL}/api/token`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + if (res.data.success) { + setUserData(res.data); + } + }) + .catch((err) => { + console.log(err); + }); + } else { + setUserData(null); + } + }, [path]); // form to send image change request const [form, setForm] = useState({ id: user._id, @@ -153,7 +176,7 @@ const Dashboard = () => { setError("User data is not available."); } }, [user._id]); - + console.log(user) const handleDelete = (id) => { const val = confirm("Are you sure you want to delete this recipe?"); if (val) { @@ -371,10 +394,13 @@ const Dashboard = () => {

{user.firstName} {user.lastName}

+ {userData && ( +

- Followers: {user.followers.length} Following:{" "} - {user.following.length} + Followers: {userData.user.followers.length} Following:{" "} + {userData.user.following.length}

+ )} {/*

Meggings kinfolk echo park stumptown DIY, kale chips beard diff --git a/frontend/src/Pages/Profile.jsx b/frontend/src/Pages/Profile.jsx index b930e4b4..365cdbda 100644 --- a/frontend/src/Pages/Profile.jsx +++ b/frontend/src/Pages/Profile.jsx @@ -1,12 +1,12 @@ import { useState, useEffect } from 'react' -import { useParams } from 'react-router-dom' +import { useParams, useLocation } from 'react-router-dom' import axios from 'axios' export default function UserProfile() { const { id } = useParams() // Get the user ID from the URL params const backendURL = import.meta.env.VITE_BACKEND_URL const token = JSON.parse(localStorage.getItem("tastytoken")) // Token still comes from local storage for authentication - + const [user, setUser] = useState(null) const [username, setUsername] = useState("ChefJulia") const [bio, setBio] = useState("Passionate about creating delicious, healthy recipes that anyone can make!") const [imagePreview, setImagePreview] = useState("/placeholder.svg?height=128&width=128") @@ -18,6 +18,30 @@ export default function UserProfile() { const [activeTab, setActiveTab] = useState('recipes') const [loading, setLoading] = useState(false) const [error, setError] = useState(null) + const path = useLocation().pathname; + useEffect(() => { + let token = localStorage.getItem("tastytoken"); + if (token) { + token = JSON.parse(token); + axios + .get(`${backendURL}/api/token`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + .then((res) => { + if (res.data.success) { + setUser(res.data); + } + }) + .catch((err) => { + console.log(err); + }); + } else { + setUser(null); + } + }, [path]); + useEffect(() => { // Fetch user information @@ -34,6 +58,8 @@ export default function UserProfile() { setImagePreview(res.data.profile) setFollowingCount(res.data.following.length) setFollowers(res.data.followers.length) + console.log(user) + setFollowing(res.data.followers.includes(user.user._id)) }) .catch((err) => { console.error("Error fetching user data", err) @@ -82,12 +108,20 @@ export default function UserProfile() { fetchUserImage() fetchRecipes() fetchLikedRecipes() // Call the fetchLikedRecipes function to get liked recipes - }, [id, backendURL, token]) // Use id, backendURL, and token in dependencies + }, [id, backendURL, token, user]) // Use id, backendURL, and token in dependencies - const handleFollow = () => { - setFollowing(!following) - setFollowers(followers + (following ? -1 : 1)) - } + const handleFollow = async () => { + try { + await axios.post(`${backendURL}/api/follow`, + { username: JSON.parse(localStorage.getItem("username")), userId: id }, + { headers: { Authorization: `Bearer ${token}` }} + ); + setFollowing(!following); + setFollowers(followers + (following ? -1 : 1)); + } catch (err) { + console.error("Error updating follow status", err); + } + }; return (

From 0070fe89b2e1baf5ac7748623f947d463040545b Mon Sep 17 00:00:00 2001 From: Ikki Date: Sat, 9 Nov 2024 17:59:53 +0530 Subject: [PATCH 4/6] profile-banner-gradient-added --- frontend/src/Pages/Profile.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Pages/Profile.jsx b/frontend/src/Pages/Profile.jsx index 365cdbda..b468c4f3 100644 --- a/frontend/src/Pages/Profile.jsx +++ b/frontend/src/Pages/Profile.jsx @@ -126,7 +126,7 @@ export default function UserProfile() { return (
{/* Banner and Profile Picture */} -
+
Date: Sat, 9 Nov 2024 18:30:53 +0530 Subject: [PATCH 5/6] merge-fix --- frontend/src/Pages/Profile.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/Pages/Profile.jsx b/frontend/src/Pages/Profile.jsx index 96c626f4..083caa56 100644 --- a/frontend/src/Pages/Profile.jsx +++ b/frontend/src/Pages/Profile.jsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react +import { useState, useEffect } from 'react' import { useParams, useLocation } from 'react-router-dom' import axios from 'axios' @@ -134,7 +134,10 @@ export default function UserProfile() {
{/* Banner and Profile Picture */} -
+
+ + +
Date: Sat, 9 Nov 2024 22:59:06 +0530 Subject: [PATCH 6/6] Update Dashboard.jsx --- frontend/src/Pages/Dashboard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Pages/Dashboard.jsx b/frontend/src/Pages/Dashboard.jsx index b011b0e9..a02666e7 100644 --- a/frontend/src/Pages/Dashboard.jsx +++ b/frontend/src/Pages/Dashboard.jsx @@ -176,7 +176,7 @@ const Dashboard = () => { setError("User data is not available."); } }, [user._id]); - console.log(user) + const handleDelete = (id) => { const val = confirm("Are you sure you want to delete this recipe?"); if (val) {