Skip to content

Commit

Permalink
Merge branch 'master' into signup-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
IkkiOcean authored Nov 9, 2024
2 parents 9d9ebb5 + 7dde644 commit b59ee48
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 1 deletion.
3 changes: 3 additions & 0 deletions frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import ResetPassword from "./Pages/ResetPassword.jsx";
import ForgotPassword from "./Pages/ForgotPassword.jsx";
import RecipeSuggestions from "./Pages/RecipeSuggestions.jsx";
import EmailVerification from "./Pages/EmailVerification.jsx"
import UserProfile from "./Pages/Profile.jsx";

function App() {
const [showScroll, setShowScroll] = useState(false);

Expand Down Expand Up @@ -81,6 +83,7 @@ function App() {
element={<Recipes key={"Healthy"} type="Healthy" />}
/>
<Route path="/user/:id" element={<Dashboard />} />
<Route path="/profile/:id" element={<UserProfile />} />
<Route path="/user/:id/new/recipe" element={<AddRecipe />} />
<Route path="/user/:id/update/recipe" element={<UpdateRecipe />} />
<Route path="/recipe/:id" element={<OneRecipe />} />
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/Components/Testimonial.jsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<div className="rounded-lg hover:shadow-xl p-4 flex flex-col justify-between h-full transition-shadow duration-300 transform hover:scale-105 shadow-[0_2px_10px_rgba(0,0,0,0.3)] bg-white">
<div>
<div className="flex items-center mb-4">
<img src={review.userId.profile} alt={review.userId.firstName} className="w-16 h-16 rounded-full border-2 mr-4 object-cover" />
<div>
<Link to={`/profile/${review.userId._id}`}>
<h3 className="font-semibold text-lg text-gray-800">{review.userId.firstName} {review.userId.lastName}</h3>
</Link>
<p className="text-sm text-red-600">{review.role}</p>
</div>
</div>
Expand Down
211 changes: 211 additions & 0 deletions frontend/src/Pages/Profile.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="bg-white min-h-screen">
{/* Banner and Profile Picture */}
<div className="relative h-48 bg-red-700">
<div className="absolute -bottom-16 left-8">
<img
src={imagePreview}
alt={username}
className="w-32 h-32 rounded-full border-4 border-white"
/>
</div>
</div>

{/* User Info */}
<div className="pt-20 px-8">
<div className="flex justify-between items-start">
<div>
<h1 className="text-2xl font-bold">{username}</h1>
<p className="text-gray-600 mt-1">{bio}</p>
</div>
<button
className={`px-4 py-2 rounded-full ${
following
? 'bg-white text-red-700 border border-red-700 hover:bg-red-50'
: 'bg-red-700 text-white hover:bg-red-800'
}`}
onClick={handleFollow}
>
{following ? 'Following' : 'Follow'}
</button>
</div>

{/* User Stats */}
<div className="flex gap-4 mt-4 text-sm">
<span><strong>{recipes.length}</strong> Recipes</span>
<span><strong>{followers}</strong> Followers</span>
<span><strong>{followingCount}</strong> Following</span>
</div>
</div>

{/* Tabs for Recipes and Liked Recipes */}
<div className="mt-8">
<div className="flex border-b border-gray-200">
<button
className={`px-4 py-2 ${activeTab === 'recipes' ? 'border-b-2 border-red-700 text-red-700' : 'text-gray-500'}`}
onClick={() => setActiveTab('recipes')}
>
Recipes
</button>
<button
className={`px-4 py-2 ${activeTab === 'liked' ? 'border-b-2 border-red-700 text-red-700' : 'text-gray-500'}`}
onClick={() => setActiveTab('liked')}
>
Liked Recipes
</button>
</div>

{activeTab === 'recipes' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{loading ? (
<p>Loading recipes...</p>
) : error ? (
<p className="text-red-500">{error}</p>
) : recipes.length > 0 ? (
recipes.map((recipe) => (
<div key={recipe.id} className="bg-white rounded-lg shadow-md overflow-hidden">
<img
src={recipe.image || "/placeholder.svg?height=200&width=300"}
alt={recipe.name}
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h3 className="font-bold text-lg mb-2">{recipe.name}</h3>
<p className="text-gray-600 text-sm mb-4">{recipe.description}</p>
<div className="flex justify-between text-sm text-gray-500">
<span>{recipe.rating}</span>
<span>👍 {recipe.likes}</span>
</div>
</div>
</div>
))
) : (
<p className="text-gray-500">No recipes posted</p>
)}
</div>
)}

{activeTab === 'liked' && (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 p-4">
{loading ? (
<p>Loading liked recipes...</p>
) : error ? (
<p className="text-red-500">{error}</p>
) : likedRecipes.length > 0 ? (
likedRecipes.map((recipe) => (
<div key={recipe.id} className="bg-white rounded-lg shadow-md overflow-hidden">
<img
src={recipe.image || "/placeholder.svg?height=200&width=300"}
alt={recipe.name}
className="w-full h-48 object-cover"
/>
<div className="p-4">
<h3 className="font-bold text-lg mb-2">{recipe.name}</h3>
<p className="text-gray-600 text-sm mb-4">{recipe.description}</p>
<div className="flex justify-between text-sm text-gray-500">
<span>{recipe.rating}</span>
<span>👍 {recipe.likes}</span>
</div>
</div>
</div>
))
) : (
<p className="text-gray-500">No liked recipes</p>
)}
</div>
)}
</div>
</div>
)
}

0 comments on commit b59ee48

Please sign in to comment.