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
- {
@@ -251,17 +267,17 @@ const AddRecipePage = () => {
Calories
- setCalories(e.target.value)}
+ value={calories}
+ onChange={(e) => setCalories(Math.max(1, e.target.value))}
onPaste={preventPasteNegative}
onKeyPress={preventMinus}
className="w-full p-3 border rounded-lg"
- InputLabelProps={{
- shrink: true,
- }}
+ InputProps={{ inputProps: { min: 1 } }}
/>
@@ -273,12 +289,11 @@ const AddRecipePage = () => {
Type
-
diff --git a/frontend/src/pages/community.js b/frontend/src/pages/community.js
index 05b3db28..9ed9ac62 100644
--- a/frontend/src/pages/community.js
+++ b/frontend/src/pages/community.js
@@ -47,7 +47,7 @@ const Community = ({ filter }) => {
{filteredRecipes.length > 0 ? (
filteredRecipes.map((recipe) => (
-
+
))
) : (
diff --git a/frontend/src/pages/edit.js b/frontend/src/pages/edit.js
index 7534d183..74d03289 100644
--- a/frontend/src/pages/edit.js
+++ b/frontend/src/pages/edit.js
@@ -1,17 +1,17 @@
-import React, { useState, useEffect } from 'react';
-import { useNavigate, useLocation } from 'react-router-dom';
-import { doc, setDoc, getDoc } from 'firebase/firestore';
+import React, { useState, useEffect } from "react";
+import { useNavigate, useLocation } from "react-router-dom";
+import { doc, setDoc, getDoc } from "firebase/firestore";
import FormControl from "@mui/material/FormControl";
import MenuItem from "@mui/material/MenuItem";
import Select from "@mui/material/Select";
-import { db, auth } from '../components/firebase';
+import { db, auth } from "../components/firebase";
import { TextField } from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import AddIcon from "@mui/icons-material/Add";
import IconButton from "@mui/material/IconButton";
-import { getStorage, ref, uploadBytes, getDownloadURL } from 'firebase/storage';
-import Loading from '../animations/loading';
+import { getStorage, ref, uploadBytes, getDownloadURL } from "firebase/storage";
+import Loading from "../animations/loading";
const EditRecipe = () => {
const location = useLocation();
@@ -29,16 +29,18 @@ const EditRecipe = () => {
ingredients: initialIngredients,
} = location.state || {};
- const [title, setTitle] = useState(initialTitle || '');
- const [level, setLevel] = useState(initialLevel || '');
- const [time, setTime] = useState(initialTime || '');
- const [calories, setCalories] = useState(initialCalories || '');
- const [type, setType] = useState(initialType || '');
- const [rating, setRating] = useState(initialRating || '');
- const [description, setDescription] = useState(initialDescription || '');
+ const [title, setTitle] = useState(initialTitle || "");
+ const [level, setLevel] = useState(initialLevel || "");
+ const [time, setTime] = useState(initialTime || "");
+ const [calories, setCalories] = useState(initialCalories || "");
+ const [type, setType] = useState(initialType || "");
+ const [rating, setRating] = useState(initialRating || "");
+ const [description, setDescription] = useState(initialDescription || "");
const [image, setImage] = useState(null);
- const [imageUrl, setImageUrl] = useState(initialImageUrl || '');
- const [ingredients, setIngredients] = useState(initialIngredients || [{ name: '', amount: '' }]);
+ const [imageUrl, setImageUrl] = useState(initialImageUrl || "");
+ const [ingredients, setIngredients] = useState(
+ initialIngredients || [{ name: "", amount: "" }]
+ );
const [isSubmitting, setIsSubmitting] = useState(false);
const preventPasteNegative = (e) => {
@@ -59,7 +61,7 @@ const EditRecipe = () => {
useEffect(() => {
if (recId) {
const fetchRecipe = async () => {
- const docRef = doc(db, 'users', auth.currentUser.uid, 'recipes', recId);
+ const docRef = doc(db, "users", auth.currentUser.uid, "recipes", recId);
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const data = docSnap.data();
@@ -71,7 +73,7 @@ const EditRecipe = () => {
setRating(data.rating);
setDescription(data.description);
setImageUrl(data.imageUrl);
- setIngredients(data.ingredients || [{ name: '', amount: '' }]);
+ setIngredients(data.ingredients || [{ name: "", amount: "" }]);
}
};
fetchRecipe();
@@ -91,7 +93,7 @@ const EditRecipe = () => {
};
const handleAddIngredient = () => {
- setIngredients([...ingredients, { name: '', amount: '' }]);
+ setIngredients([...ingredients, { name: "", amount: "" }]);
};
const handleRemoveIngredient = (index) => {
@@ -106,7 +108,7 @@ const EditRecipe = () => {
try {
const user = auth.currentUser;
if (user && recId) {
- const docRef = doc(db, 'users', user.uid, 'recipes', recId);
+ const docRef = doc(db, "users", user.uid, "recipes", recId);
const recipeData = {
title,
@@ -130,21 +132,21 @@ const EditRecipe = () => {
}
await setDoc(docRef, recipeData, { merge: true });
- console.log('Recipe updated successfully');
- navigate('/recipes');
+ console.log("Recipe updated successfully");
+ navigate("/recipes");
} else {
- console.error('User is not authenticated or recipe ID is missing');
+ console.error("User is not authenticated or recipe ID is missing");
}
} catch (error) {
- console.error('Error updating recipe:', error);
- alert('Error updating recipe: ' + error.message);
+ console.error("Error updating recipe:", error);
+ alert("Error updating recipe: " + error.message);
} finally {
setIsSubmitting(false);
}
};
if (isSubmitting) {
- return
+ return
;
}
return (
@@ -153,7 +155,34 @@ const EditRecipe = () => {
Edit Recipe