diff --git a/.firebase/hosting.ZnJvbnRlbmQvYnVpbGQ.cache b/.firebase/hosting.ZnJvbnRlbmQvYnVpbGQ.cache index ae60d6e5..018d4a48 100644 --- a/.firebase/hosting.ZnJvbnRlbmQvYnVpbGQ.cache +++ b/.firebase/hosting.ZnJvbnRlbmQvYnVpbGQ.cache @@ -1,24 +1,24 @@ -index.html,1721638827092,993f17c9dfd28503b796ffd7cecd9f14d95e4aaa647bb068de9b39f26aabd74f -manifest.json,1721638809613,4e1d3adad9ce7e17c0d13fac197f1c27091906f4ecf3909c9f51247bff345118 -asset-manifest.json,1721638827126,25f1d6fa8fdcd670d2b86302d079aefc848c11c2c32805e0d62920f5f011c6bf -logo192.png,1721638809613,f66564670b5bfe49f3f04a5b63107bae60f4a7736f1d3b83aa0115f7acf45150 -static/media/finn-the-human-duotone-svgrepo-com.c2af141b053a588ab779463a816a2318.svg,1721638827093,4bf06ca5478d8acf94f058532105301e7a15788e4ec5cab21c2fde365db6f5fa -favicon.ico,1721638809613,5b38de901f18e095d7155d9bd0c6e41618702287d5cb786ee13e9cebfaa99873 -static/media/filledheart.172e627cb83b2ea56618.png,1721638827093,aac23cadfc2d053c94915519c1498b31eb0c6fc0a2fce1a3049da282365f73c7 -static/js/main.437e403f.js.LICENSE.txt,1721638827093,ad1d5e6af16ffe8e92a428dabf40f561b12ed268b0211d360fc6e7be765028d3 -static/media/bin.a6fd29509719f079bfd7.png,1721638827093,c797472c643f6d7334bd1d4a8e624e0c1da0b5485c05206ef4d36aad63dc664c -static/css/main.4bf6b6d0.css.map,1721638827126,aa20225782aebc9af55e7e83978b28bc3b1e71cab1c017739c0f9c27b8fd6e61 -static/css/main.4bf6b6d0.css,1721638827094,b8d157edee3f3ecf062c8b896de4af60e56da9378b5852a081c46071295763a8 -static/media/teenager.bfacfcc1a78c8289e4aa.png,1721638827093,0cc47e67e35ba42db25364f77407b8125ac118827d89c7bfeb6118d4c368d271 -static/media/heart.c412cbaead4744166c20.png,1721638827093,e51cad9407d658e54ba52d6238f5a02a84ca54eb83c938d57f27c63fbe99be89 -strawberry-cake.png,1721638809614,d9f87d425d830c81a8c691be543a158d4d8b65516e11b7ca1ae42374a92b78ce -static/media/young.46133b84dc73f8fd4e5d.png,1721638827093,8d68471a561553b4dc5cdc874f9bf173223ee464097a18bb8116cfa99b48cfae -static/media/strawberrycake.0d5ebe11edd5bf600704.png,1721638827093,d9f87d425d830c81a8c691be543a158d4d8b65516e11b7ca1ae42374a92b78ce -static/media/panda.0253ba0e4ab6e6b77c68.png,1721638827093,57b14170fed336abd491746bb7e3aee2379824606a2e7d411cbbb667266cc7a1 -static/media/google.cadee8cdb0c0afb1a713.png,1721638827093,60b6f749a9daafdc12e3cdbc6347b7e1ac0ab37e4e4008eb8d9de22601c43080 -static/media/dog.f9eb1be80a2c19b336b6.png,1721638827093,0c702e5219385113707a4b9f7be3174bfc3a887709a8db9973b32d36378c9373 -static/media/cat.077086a0654634be593c.png,1721638827093,29b869731eb086ee1df0dea2689226cf9944a394d3b402e96dd3b13f8c57d607 -static/media/blueberry-pie.f57f327ce0707ce0306f.jpg,1721638827093,88afba500cf8b0535c304a2c8c7b486d95fc7564720402feaea4d6a1cf8294d3 -static/media/raspberrytart.9cdf8b62e002147aa401.jpg,1721638827093,c8acd598dcadde275a334e0867e9cc74e1bc1b2adacdc62daaae82b8d47a2ced -static/js/main.437e403f.js,1721638827126,8fb0dfcc80bafc430b79309f6ed4c3aabdd8aa0c196c0d7c57f9e8a20f06ee27 -static/js/main.437e403f.js.map,1721638827129,7657a19943340887c10c9e2f10cffc6dd0fd9a76a17c3968874c31f0aac8285c +index.html,1721729005502,70c112ad4195e42b1ad7f19cd778a56fa3d46d68eab5f9abc850288a41c30d5a +asset-manifest.json,1721729005534,f8d6a3e3ee17ac5b3a83303f5274ae5c0793530df038d77229aa6bd0ebbf0240 +manifest.json,1721728988354,4e1d3adad9ce7e17c0d13fac197f1c27091906f4ecf3909c9f51247bff345118 +logo192.png,1721728988354,f66564670b5bfe49f3f04a5b63107bae60f4a7736f1d3b83aa0115f7acf45150 +favicon.ico,1721728988354,5b38de901f18e095d7155d9bd0c6e41618702287d5cb786ee13e9cebfaa99873 +static/media/finn-the-human-duotone-svgrepo-com.c2af141b053a588ab779463a816a2318.svg,1721729005503,4bf06ca5478d8acf94f058532105301e7a15788e4ec5cab21c2fde365db6f5fa +static/media/filledheart.172e627cb83b2ea56618.png,1721729005503,aac23cadfc2d053c94915519c1498b31eb0c6fc0a2fce1a3049da282365f73c7 +static/media/bin.a6fd29509719f079bfd7.png,1721729005503,c797472c643f6d7334bd1d4a8e624e0c1da0b5485c05206ef4d36aad63dc664c +static/js/main.6e8e5b78.js.LICENSE.txt,1721729005503,ad1d5e6af16ffe8e92a428dabf40f561b12ed268b0211d360fc6e7be765028d3 +static/css/main.22bd4496.css,1721729005504,0bf994317306284e45784c49719b2d5b94d0abb65bd882a03f853ef2bb49f8da +static/css/main.22bd4496.css.map,1721729005534,6a6595fc93f360a814ca9eaaf31fa0b23e56e589939a3a12938ea0272896fce7 +static/media/young.46133b84dc73f8fd4e5d.png,1721729005503,8d68471a561553b4dc5cdc874f9bf173223ee464097a18bb8116cfa99b48cfae +static/media/strawberrycake.0d5ebe11edd5bf600704.png,1721729005503,d9f87d425d830c81a8c691be543a158d4d8b65516e11b7ca1ae42374a92b78ce +strawberry-cake.png,1721728988355,d9f87d425d830c81a8c691be543a158d4d8b65516e11b7ca1ae42374a92b78ce +static/media/heart.c412cbaead4744166c20.png,1721729005503,e51cad9407d658e54ba52d6238f5a02a84ca54eb83c938d57f27c63fbe99be89 +static/media/teenager.bfacfcc1a78c8289e4aa.png,1721729005503,0cc47e67e35ba42db25364f77407b8125ac118827d89c7bfeb6118d4c368d271 +static/media/panda.0253ba0e4ab6e6b77c68.png,1721729005503,57b14170fed336abd491746bb7e3aee2379824606a2e7d411cbbb667266cc7a1 +static/media/google.cadee8cdb0c0afb1a713.png,1721729005503,60b6f749a9daafdc12e3cdbc6347b7e1ac0ab37e4e4008eb8d9de22601c43080 +static/media/dog.f9eb1be80a2c19b336b6.png,1721729005503,0c702e5219385113707a4b9f7be3174bfc3a887709a8db9973b32d36378c9373 +static/media/cat.077086a0654634be593c.png,1721729005503,29b869731eb086ee1df0dea2689226cf9944a394d3b402e96dd3b13f8c57d607 +static/media/blueberry-pie.f57f327ce0707ce0306f.jpg,1721729005503,88afba500cf8b0535c304a2c8c7b486d95fc7564720402feaea4d6a1cf8294d3 +static/media/raspberrytart.9cdf8b62e002147aa401.jpg,1721729005503,c8acd598dcadde275a334e0867e9cc74e1bc1b2adacdc62daaae82b8d47a2ced +static/js/main.6e8e5b78.js,1721729005534,9d552ceae6c8c472db2bd92e6d5494729595ab52f89072bdf311840a35cbf620 +static/js/main.6e8e5b78.js.map,1721729005535,80410ce4dc94efff3da51a0a1ef6d23c4d61c18fe8494eb6c0f83fd3e099e6f7 diff --git a/frontend/src/App.js b/frontend/src/App.js index d87ed437..aa56ab43 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,123 +1,128 @@ -import React from 'react'; -import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; -import MainLayout from './components/mainlayout'; -import Signup from './pages/signup'; -import LogIn from './pages/login'; -import ProtectedRoute from './components/protectedRoute'; -import AddRecipePage from './pages/addRecipePage'; -import Home from './pages/home'; -import Favourites from './pages/favourites'; -import Community from './pages/community'; -import { SearchProvider } from './components/searchContext'; -import Chatbot from './components/chatbot'; -import Profile from './pages/profile'; -import EditPage from './pages/edit'; -import ViewRecipePage from './pages/viewRecipePage'; -import Courses from './pages/courses'; -import AddEvent from './pages/addEventPage'; +import React, { useEffect } from "react"; +import { + BrowserRouter as Router, + Route, + Routes, + useNavigate, +} from "react-router-dom"; +import MainLayout from "./components/mainlayout"; +import Signup from "./pages/signup"; +import LogIn from "./pages/login"; +import ProtectedRoute from "./components/protectedRoute"; +import AddRecipePage from "./pages/addRecipePage"; +import Home from "./pages/home"; +import Favourites from "./pages/favourites"; +import Community from "./pages/community"; +import { SearchProvider } from "./components/searchContext"; +import Chatbot from "./components/chatbot"; +import Profile from "./pages/profile"; +import EditPage from "./pages/edit"; +import ViewRecipePage from "./pages/viewRecipePage"; +import Courses from "./pages/courses"; +import AddEvent from "./pages/addEventPage"; const App = () => { + const navigate = useNavigate(); + + useEffect(() => { + const user = sessionStorage.getItem("sessionKey"); + if (!user) { + navigate("/login"); + } + }, [navigate]); return ( - - } /> - } /> - } /> - } /> - } /> - - - - - } - /> - } - /> - - - - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - - - - - } - /> - } - /> - - - - } - /> - } - /> - - - - - } - /> - } - /> - - - - - } - /> - } - /> - + + } /> + } /> + } /> + } /> + } /> + + + + + } + /> + } + /> + + + + } + /> + } + /> + } />} + /> + } />} + /> + + + + + } + /> + } + /> + + + + } + /> + } + /> + + + + + } + /> + } + /> + + + + + } + /> + } + /> + ); }; diff --git a/frontend/src/components/cardfav.js b/frontend/src/components/cardfav.js index c100cb4d..58dfdd35 100644 --- a/frontend/src/components/cardfav.js +++ b/frontend/src/components/cardfav.js @@ -9,7 +9,7 @@ import pen from '../assets/pen.png'; import { PiBowlFoodBold } from 'react-icons/pi'; import Modal from '../components/modal'; -const CardFav = ({ title, level, time, calories, type, rating, description, isFavourited, recId, imageUrl, ingredients }) => { +const CardFav = ({ title, level, time, calories, type, rating, description, isFavourited, recId, imageUrl, ingredients, showEditDeleteButtons = true }) => { const [isFavourite, setIsFavourite] = useState(isFavourited); const [docId, setDocId] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); @@ -113,15 +113,7 @@ const CardFav = ({ title, level, time, calories, type, rating, description, isFa return (
-
- +
- +
+
+ {showEditDeleteButtons && ( + <> + + + + )}
{imageUrl ? ( diff --git a/frontend/src/components/mainlayout.js b/frontend/src/components/mainlayout.js index 99b6304d..032e9ac9 100644 --- a/frontend/src/components/mainlayout.js +++ b/frontend/src/components/mainlayout.js @@ -5,7 +5,7 @@ import SearchBar from "./searchBar"; import { FaFilter } from "react-icons/fa"; import { useLocation } from "react-router-dom"; import { auth, db } from "../components/firebase"; -import { collection, query, getDocs } from "firebase/firestore"; +import { collection, query, getDocs, onSnapshot } from "firebase/firestore"; import Community from "../pages/community"; const recipeTypes = { @@ -27,23 +27,29 @@ const MainLayout = ({ children }) => { const isEditRecipePage = location.pathname === "/editRecipe"; useEffect(() => { - const fetchRecipes = async () => { - const user = auth.currentUser; - if (user) { - try { - const q = query(collection(db, "users", user.uid, "recipes")); - const querySnapshot = await getDocs(q); - const fetchedRecipes = querySnapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() })); - setRecipes(fetchedRecipes); - } catch (error) { - console.error("Error fetching recipes:", error); - } - } - }; + const user = auth.currentUser; + if (user) { + const q = query(collection(db, "users", user.uid, "recipes")); + const unsubscribe = onSnapshot(q, (querySnapshot) => { + const fetchedRecipes = querySnapshot.docs.map((doc) => { + const data = doc.data(); + return { + id: doc.id, + ...data, + createdAt: data.createdAt.toDate() + }; + }); - fetchRecipes(); - }, []); + fetchedRecipes.sort((a, b) => b.createdAt - a.createdAt); + + setRecipes(fetchedRecipes); + setFilteredRecipes(fetchedRecipes); + }); + + return () => unsubscribe(); + } + }, []); useEffect(() => { let newFilteredRecipes = recipes; @@ -54,8 +60,8 @@ const MainLayout = ({ children }) => { } else if (filter === "DateCreated") { newFilteredRecipes = recipes .slice() - .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); - console.log(newFilteredRecipes) + .sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); + console.log(newFilteredRecipes); } setFilteredRecipes(newFilteredRecipes); @@ -94,7 +100,7 @@ const MainLayout = ({ children }) => { className="border border-gray-300 rounded p-2 shadow-sm" > - + {Object.keys(recipeTypes).map(type => ( ))} diff --git a/frontend/src/components/sidebar.js b/frontend/src/components/sidebar.js index 53dbaea7..affdf08e 100644 --- a/frontend/src/components/sidebar.js +++ b/frontend/src/components/sidebar.js @@ -15,7 +15,7 @@ import { getDoc, collection, query, - getDocs, + getDocs, onSnapshot } from "firebase/firestore"; import { auth, db } from "../components/firebase"; import { Link, useNavigate } from "react-router-dom"; @@ -45,9 +45,17 @@ const Sidebar = () => { } }; - useEffect(() => { + useEffect(() => { const unsubscribe = auth.onAuthStateChanged((user) => { fetchUserData(user); + if (user) { + const q = query(collection(db, "users", user.uid, "recipes")); + const unsubscribeRecipes = onSnapshot(q, (querySnapshot) => { + setRecipeCount(querySnapshot.size); + }); + // Cleanup listener on unmount + return () => unsubscribeRecipes(); + } }); const handleResize = () => { @@ -62,6 +70,7 @@ const Sidebar = () => { }; }, []); + const toggleSidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; diff --git a/frontend/src/pages/addRecipePage.js b/frontend/src/pages/addRecipePage.js index 63830c81..5baf04a2 100644 --- a/frontend/src/pages/addRecipePage.js +++ b/frontend/src/pages/addRecipePage.js @@ -88,18 +88,31 @@ const AddRecipePage = () => { }; const handleRemoveIngredient = (index) => { - const newIngredients = [...ingredients]; - newIngredients.splice(index, 1); - setIngredients(newIngredients); + if (ingredients.length > 1) { + const newIngredients = [...ingredients]; + newIngredients.splice(index, 1); + setIngredients(newIngredients); + } }; const handleSubmit = async (event) => { event.preventDefault(); setIsSubmitting(true); + + const invalidIngredients = ingredients.some( + (ingredient) => !ingredient.name.trim() || !ingredient.amount.trim() + ); + + if (invalidIngredients) { + // alert("Please provide both name and amount for all ingredients."); + setIsSubmitting(false); + return; + } + try { const user = auth.currentUser; if (user) { - const isNewRecipe = !docId; + const isNewRecipe = !docId; const userRecipeRef = isNewRecipe ? doc(collection(db, "users", user.uid, "recipes")) : doc(db, "users", user.uid, "recipes", recId); @@ -107,8 +120,8 @@ const AddRecipePage = () => { const recipeData = { title, level, - time, - calories, + time: Math.max(1, time), + calories: Math.max(1, calories), type, rating, description, @@ -126,7 +139,10 @@ const AddRecipePage = () => { if (image) { const storage = getStorage(); - const storageRef = ref(storage, `recipes/${user.uid}/${newRecId}/${image.name}`); + const storageRef = ref( + storage, + `recipes/${user.uid}/${newRecId}/${image.name}` + ); console.log("Uploading image:", image.name); await uploadBytes(storageRef, image); console.log("Image uploaded successfully"); @@ -156,7 +172,7 @@ const AddRecipePage = () => { }; if (isSubmitting) { - return ; + return ; } return ( @@ -188,7 +204,8 @@ const AddRecipePage = () => { > Recipe Title - { setType(e.target.value)} className="block text-gray-700 text-sm font-bold mb-2" > @@ -300,12 +315,11 @@ const AddRecipePage = () => { Rating (1-5) - @@ -339,12 +354,16 @@ const AddRecipePage = () => { placeholder="Amount" value={ingredient.amount} onPaste={preventPasteNegative} + required onChange={(e) => handleIngredientChange(index, "amount", e.target.value) } className="w-full p-3 border rounded-lg ml-2" /> - handleRemoveIngredient(index)}> + handleRemoveIngredient(index)} + disabled={ingredients.length === 1} + > @@ -359,7 +378,8 @@ const AddRecipePage = () => { > Recipe Instructions -