From 4d2b33b67e772983161ecd9508dfcbc57fe234f4 Mon Sep 17 00:00:00 2001 From: jhcp Date: Wed, 29 May 2024 19:31:01 -0700 Subject: [PATCH 01/27] Changed theme to match My Groups Page --- frontend/src/components/Basket.tsx | 2 +- frontend/src/components/BasketItem.tsx | 2 +- frontend/src/components/EditItem.tsx | 4 +- frontend/src/components/ItemGroup.tsx | 49 +++++----- frontend/src/components/NewItemOptions.tsx | 6 +- frontend/src/pages/IndividualGroupPage.tsx | 6 +- frontend/src/pages/ItemsPage.tsx | 102 +++++++++++++-------- frontend/src/styles/ItemGroup.css | 7 ++ 8 files changed, 101 insertions(+), 77 deletions(-) create mode 100644 frontend/src/styles/ItemGroup.css diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index 14892fd..901c26b 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -44,7 +44,7 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { .then((res) => res.status === 200 ? res.json() - : Promise.reject(`Error code ${res.status}`) + : Promise.reject(`Error code ${res.status}`), ) .then((data) => { setBasket({ diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index 0402896..c9d4db2 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -33,7 +33,7 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { .then((res) => res.status === 200 ? res.json() - : Promise.reject(`Error code ${res.status}`) + : Promise.reject(`Error code ${res.status}`), ) .then((data) => { setItem({ diff --git a/frontend/src/components/EditItem.tsx b/frontend/src/components/EditItem.tsx index bc0c92f..b2d0058 100644 --- a/frontend/src/components/EditItem.tsx +++ b/frontend/src/components/EditItem.tsx @@ -302,9 +302,9 @@ const EditItem: React.FC = ({ itemId }) => { - - - } - /> - + ITEMS + + + + + - - - + + switch to groups + + + + console.log("Absolutely not implemented yet.")} + placeholder="search for groups" + width="500px" + /> + + {/* Solid line b/t Header & Grid */} + {!loading ? ( diff --git a/frontend/src/styles/ItemGroup.css b/frontend/src/styles/ItemGroup.css new file mode 100644 index 0000000..ef5c27b --- /dev/null +++ b/frontend/src/styles/ItemGroup.css @@ -0,0 +1,7 @@ +.item_group-container { + border-color: #c1c1c1; + border-width: 5px; + width: full; + background-color: var(--col-bright); + border-radius: 30px; +} From fc6ad2e42efff2c5af060e548b834a31f904627b Mon Sep 17 00:00:00 2001 From: jhcp Date: Wed, 29 May 2024 19:32:48 -0700 Subject: [PATCH 02/27] Removed unused components --- frontend/src/pages/ItemsPage.tsx | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/ItemsPage.tsx b/frontend/src/pages/ItemsPage.tsx index 10f3c4d..1c67ea2 100644 --- a/frontend/src/pages/ItemsPage.tsx +++ b/frontend/src/pages/ItemsPage.tsx @@ -1,19 +1,7 @@ import React, { useEffect } from "react"; -import { - Box, - Button, - Flex, - HStack, - Heading, - Icon, - Input, - InputGroup, - InputLeftElement, - VStack, -} from "@chakra-ui/react"; -import { ArrowForwardIcon, SearchIcon } from "@chakra-ui/icons"; +import { Box, Flex, HStack, Heading, Icon, VStack } from "@chakra-ui/react"; import ItemGroup from "../components/ItemGroup"; -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { IUser } from "../../../backend/models/userSchema"; import { IGroup } from "../../../backend/models/groupSchema"; import { IoIosSwap } from "react-icons/io"; From 26c3191e2fe93cb459d5d73da86666a037c1835d Mon Sep 17 00:00:00 2001 From: jhcp Date: Thu, 30 May 2024 07:29:28 -0700 Subject: [PATCH 03/27] Prep merge --- frontend/src/App.tsx | 12 ++++++------ frontend/src/components/Basket.tsx | 4 +++- frontend/src/pages/IndividualGroupPage.tsx | 15 +++++++++++---- frontend/src/pages/MyGroupsPage.tsx | 10 ++++++---- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c05e269..64f6a06 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -52,7 +52,7 @@ function App() { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - }, + } ); if (userres.status === 200) { const user = await userres.json(); @@ -113,14 +113,14 @@ function App() { /> } /> - } />{" "} + } + />{" "} {/* added route for individual group page */} - } + element={} /> { .then((res) => res.status === 200 ? res.json() - : Promise.reject(`Error code ${res.status}`), + : Promise.reject(`Error code ${res.status}`) ) .then((data) => { setBasket({ @@ -69,6 +69,8 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { const groupOwnerView = `${isOwnerView ? "auto" : "none"}`; const basketMemberView = basketObj?.memberIds?.includes(stateObj?.user?._id); + console.log(stateObj?.user?._id); + return ( (); const [group, setGroup] = useState(null); const [loading, setLoading] = useState(true); @@ -30,7 +37,7 @@ function IndividualGroupPage() { const fetchGroup = async () => { try { const fetchedGroup = await fetch( - `http://localhost:3001/groups/${groupId}`, + `http://localhost:3001/groups/${groupId}` ); if (fetchedGroup.ok) { const data = await fetchedGroup.json(); @@ -56,7 +63,7 @@ function IndividualGroupPage() { } else { throw new Error(`Failed to fetch user: ${res.statusText}`); } - }), + }) ); setMembers(fetchedMembers); } catch (err) { @@ -74,7 +81,7 @@ function IndividualGroupPage() { } else { throw new Error(`Failed to fetch basket: ${res.statusText}`); } - }), + }) ); setBaskets(fetchedBaskets); } catch (err) { diff --git a/frontend/src/pages/MyGroupsPage.tsx b/frontend/src/pages/MyGroupsPage.tsx index 6030819..b6fc8b9 100644 --- a/frontend/src/pages/MyGroupsPage.tsx +++ b/frontend/src/pages/MyGroupsPage.tsx @@ -52,7 +52,7 @@ const GroupPage: React.FC = ({ const data = await res.json(); return data; } - }, + } ); const tempGroupList = await Promise.all(groupPromises); @@ -66,8 +66,8 @@ const GroupPage: React.FC = ({ const lowerQuery = input.toLowerCase(); setFilteredGroups( groupList.filter((group) => - group.groupName.toLowerCase().includes(lowerQuery), - ), + group.groupName.toLowerCase().includes(lowerQuery) + ) ); } }; @@ -85,6 +85,8 @@ const GroupPage: React.FC = ({ } }, [stateVariable.user]); + console.log(stateVariable?.user?._id); + return ( = ({ const currentPage = Math.floor(ind / (gridDims[0] * gridDims[1])); if (currentPage + 1 != selectedPage) return null; const row = Math.floor( - (ind % (gridDims[1] * gridDims[0])) / gridDims[1], + (ind % (gridDims[1] * gridDims[0])) / gridDims[1] ); const col = ind % gridDims[1]; return ( From f90c495e7dd5d51cc1568d036a2ffb7a2b8a4af4 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 14:12:22 -0700 Subject: [PATCH 04/27] Displays member usernames on baskets > 1 member, avatar for baskets < 1 member --- frontend/src/components/Basket.tsx | 37 +++++++++++++++--------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index c723a1e..0420a7c 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -13,36 +13,31 @@ import BasketItem from "./BasketItem"; import NewItemOptions from "./NewItemOptions"; import EditBasket from "./EditBasket"; import AddFriendToBasket from "./AddFriendToBasket"; -import { fetchBasket } from "../../lib/fetches"; +import { fetchBasket, fetchMembers } from "../../lib/fetches"; import { IBasket } from "../../../backend/models/basketSchema"; import { IUser } from "../../../backend/models/userSchema"; import { ObjectId } from "mongoose"; interface Props { basketId: string; - stateObj: { user: any; token: any }; groupMembers: IUser[]; LoggedInUser: IUser | null; } -const BasketComp = ({ - basketId, - stateObj, - groupMembers, - LoggedInUser, -}: Props) => { +const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { const [basketObj, setBasket] = useState({} as IBasket); const [error, setError] = useState({ msg: "", isErrored: false, }); + const [memberNames, setMemberNames] = useState([]); useEffect(() => { fetchBasket(basketId) .then((res) => res.status === 200 ? res.json() - : Promise.reject(`Error code ${res.status}`), + : Promise.reject(`Error code ${res.status}`) ) .then((data) => { setBasket({ @@ -53,6 +48,15 @@ const BasketComp = ({ members: data.members, created: new Date(data.created), }); + fetchMembers(data.members) + .then((res) => { + let temp = []; // extract just the usernames from response + for (let i = 0; i < res.length; i++) { + temp.push(res[i].username); + } + setMemberNames(temp); + }) + .catch(() => console.log("Error loading member names")); }) .catch((err) => { console.log("Error: ", err); @@ -63,10 +67,8 @@ const BasketComp = ({ }); }, [basketId]); - const memberView = `${basketObj.members === undefined ? "none" : basketObj?.members?.length > 1 ? "auto" : "none"}`; - const basketMemberView = basketObj?.members?.includes(stateObj?.user?._id); - - console.log(stateObj?.user?._id); + // Render things differently depending on how many members are in a basket + const multiMemberView = `${memberNames.length === 1 ? "none" : "none"}`; return ( {basketObj.basketName === undefined ? "" : basketObj.basketName} - + {basketObj.items !== undefined ? ( @@ -124,8 +126,8 @@ const BasketComp = ({ - - Members: {basketObj?.members?.join(", ")} + + Members: {memberNames.join(", ")} Basket Items - @@ -168,7 +169,7 @@ const BasketComp = ({ return ( ); From 87d256257e096f5b2057d77833047b49f0d0e0c4 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 14:15:09 -0700 Subject: [PATCH 05/27] Renders avatar by color / initial now --- frontend/src/components/Basket.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index 0420a7c..b8e19b1 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -54,6 +54,7 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { for (let i = 0; i < res.length; i++) { temp.push(res[i].username); } + console.log(res); setMemberNames(temp); }) .catch(() => console.log("Error loading member names")); @@ -103,7 +104,10 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { > {basketObj.basketName === undefined ? "" : basketObj.basketName} - + {basketObj.items !== undefined ? ( From 2ab8f4b5afbc232ceb78a9b89f8992ee073debc0 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 14:15:29 -0700 Subject: [PATCH 06/27] Removed/commented out console logs bloating the console with unnecessary info --- frontend/src/components/EditGroup.tsx | 2 +- frontend/src/pages/IndividualGroupPage.tsx | 37 +++++++--------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/EditGroup.tsx b/frontend/src/components/EditGroup.tsx index 8b55e32..a55bd1a 100644 --- a/frontend/src/components/EditGroup.tsx +++ b/frontend/src/components/EditGroup.tsx @@ -60,7 +60,7 @@ const Editgroup: React.FC = ({ GroupId }) => { setEditedDesc(data.description); setEditedPub(data.privateGroup); } else { - console.error("Failed to fetch user data"); + console.error(`Failed to fetch user data for group ${GroupId}`); } } catch (error) { console.error("Error fetching user data:", error); diff --git a/frontend/src/pages/IndividualGroupPage.tsx b/frontend/src/pages/IndividualGroupPage.tsx index 8caf7c3..b254f3d 100644 --- a/frontend/src/pages/IndividualGroupPage.tsx +++ b/frontend/src/pages/IndividualGroupPage.tsx @@ -44,9 +44,6 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { const [members, setMembers] = useState([]); const [friends, setFriends] = useState([]); const navigate = useNavigate(); - console.log(LoggedInUser); - console.log(friends); - const fetchFriends = async (friendIds: string[]) => { try { const fetchedFriends = await Promise.all( @@ -100,17 +97,17 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { }; useEffect(() => { - console.log(`Loading: ${loading}`); + //console.log(`Loading: ${loading}`); if (groupId) { fetchGroup(String(groupId)) .then((group) => { - console.log(`Fetched group: ${group}`); + //console.log(`Fetched group: ${group}`); fetchGroupMembers(group as IGroup) .then(() => { - console.log(`Fetched group members: ${members}`); + //console.log(`Fetched group members: ${members}`); fetchBaskets(group as IGroup) .then(() => { - console.log(`Fetched group baskets: ${groupBaskets}`); + //console.log(`Fetched group baskets: ${groupBaskets}`); setLoading(false); }) .catch((err) => { @@ -281,24 +278,14 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { {groupBaskets && members ? ( - groupBaskets.map( - (basket) => ( - console.log(group), - console.log(basket), - ( - - ) - ) - ) + groupBaskets.map((basket) => ( + + )) ) : ( No baskets available )} From 545a74cc247d98645fed3a75121c416dce911419 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 15:01:25 -0700 Subject: [PATCH 07/27] Added EditItem & correct displaying for BasketItems --- backend/routes/itemRoutes.ts | 6 ++-- frontend/src/components/Basket.tsx | 23 +++++++++--- frontend/src/components/BasketItem.tsx | 48 +++++++------------------- frontend/src/styles/BasketItem.css | 5 --- 4 files changed, 34 insertions(+), 48 deletions(-) diff --git a/backend/routes/itemRoutes.ts b/backend/routes/itemRoutes.ts index a9c71bf..a6a29ee 100644 --- a/backend/routes/itemRoutes.ts +++ b/backend/routes/itemRoutes.ts @@ -29,11 +29,11 @@ router.get("/:itemid", async (req: Request, res: Response) => { // Use findById correctly with the id parameter from the request const itemById = await Item.findById(req.params.itemid); - // Check if group is null or undefined + // Check if item is null or undefined if (!itemById) { return res.status(404).send("No item found"); // Use return to exit the function after sending the response } - // Send the found user + // Send the found item res.send(itemById); console.log("Sent item"); } catch (error) { @@ -45,7 +45,7 @@ router.get("/:itemid", async (req: Request, res: Response) => { return res.status(404).send("No items found"); // Use return to exit the function after sending the response } - // Send the found user + // Send the found item res.send(itemsByName); console.log("Sent items"); } catch (error) { diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index b8e19b1..34d9896 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -54,7 +54,6 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { for (let i = 0; i < res.length; i++) { temp.push(res[i].username); } - console.log(res); setMemberNames(temp); }) .catch(() => console.log("Error loading member names")); @@ -69,7 +68,21 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { }, [basketId]); // Render things differently depending on how many members are in a basket - const multiMemberView = `${memberNames.length === 1 ? "none" : "none"}`; + const multiMemberView = memberNames.length !== 1; + // Determine if the logged in user is a member of the basket + // Note: Privacy wise, this is terrible design. This is logic that should be done on the backend since by only rendering/derendering + // items whether or not you're actually allowed to view them doesn't stop anyone from just checking the data that was recieved by the + // frontend (it SHOULD be that the backend either approves/declines your request based on if you're authorized). Because of the way + // we designed our database, it was nearly impossible to fix this design flaw by the time we realized (we just didn't have enough time + // to change the rest of our project since it was so late in the train). We acknowledge that this is a privacy problem and we would have + // liked to fix it but because of time constraints we were unable to. + const isMemberOfBasket = LoggedInUser + ? basketObj + ? basketObj.members + ? basketObj.members.includes(LoggedInUser?._id) + : false + : false + : false; return ( { {basketObj.basketName === undefined ? "" : basketObj.basketName} @@ -130,7 +143,7 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { - + Members: {memberNames.join(", ")} { return ( ); diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index 94117ad..e053973 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -3,13 +3,8 @@ import { useEffect, useState } from "react"; import { VscEye, VscEyeClosed } from "react-icons/vsc"; import "../styles/BasketItem.css"; import { fetchItem } from "../../lib/fetches"; - -export interface MinimalItem { - name: string; - isPrivate: boolean; - notes: string; - quantity: number; -} +import { IItem } from "../../../backend/models/itemSchema"; +import EditItem from "./EditItem"; interface Props { itemId: string; @@ -17,7 +12,7 @@ interface Props { } const BasketItem = ({ itemId, basketMemberView }: Props) => { - const [item, setItem] = useState(); + const [item, setItem] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState({ msg: "", @@ -30,15 +25,10 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { .then((res) => res.status === 200 ? res.json() - : Promise.reject(`Error code ${res.status}`), + : Promise.reject(`Error code ${res.status}`) ) .then((data) => { - setItem({ - name: data.name, - isPrivate: data.isPrivate, - notes: data.notes, - quantity: data.quantity, - }); + setItem(data); setLoading(false); }) .catch((err) => { @@ -51,9 +41,6 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { }); }, [itemId]); - // realistically this is NOT a privacy enforcer since the items are still being retrieved & saved - // on the browser, they just aren't being displayed. to fix this we'd have to do some backend - // logic, which isn't implemented yet. if (!basketMemberView && item?.isPrivate) { return; } @@ -72,14 +59,10 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { - ) : error.isErrored || - item?.notes === undefined || - item?.name === undefined || - item?.quantity === undefined ? ( + ) : error.isErrored || !item ? ( Error: {error.msg} - - Click to retry + ) : ( { {item?.notes} + + + { marginTop="5px" as={item?.isPrivate ? VscEyeClosed : VscEye} className="b-publicity" - onClick={() => { - console.log("changing data!"); - setItem({ - name: item?.name, - isPrivate: !item?.isPrivate, - notes: item?.notes, - quantity: item?.quantity, - }); - }} /> { marginBottom="5px" display={{ base: "none", md: "block" }} > - Viewable by other group members + {item.isPrivate ? "Not v" : "V"}iewable by other group members diff --git a/frontend/src/styles/BasketItem.css b/frontend/src/styles/BasketItem.css index 9d570fa..d58c245 100644 --- a/frontend/src/styles/BasketItem.css +++ b/frontend/src/styles/BasketItem.css @@ -5,8 +5,3 @@ .b-publicity { color: inherit; } - -.b-publicity:hover { - color: var(--col-tertiary); - cursor: pointer; -} From 4bdb9b304de12e99441325343e523515ebdd5dc3 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 16:16:10 -0700 Subject: [PATCH 08/27] Remove an item from basket backend route --- backend/routes/basketRoutes.ts | 30 +++++++++++++++++++++++ frontend/lib/deletes.tsx | 45 +++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/backend/routes/basketRoutes.ts b/backend/routes/basketRoutes.ts index 2a1a02d..d998d81 100644 --- a/backend/routes/basketRoutes.ts +++ b/backend/routes/basketRoutes.ts @@ -3,6 +3,7 @@ import { Request, Response } from "express"; import Basket, { IBasket } from "../models/basketSchema"; import connectDB from "../connection"; import { ObjectId } from "mongoose"; +import { IItem } from "../models/itemSchema"; const router = express.Router(); @@ -102,6 +103,35 @@ router.patch("/:id", async (req: Request, res: Response) => { } }); +router.patch("/:bid/removeitem", async (req: Request, res: Response) => { + connectDB(); + console.log("running delete"); + const { bid } = req.params; + const itemToRemove = req.body; + + try { + const b: IBasket | null = await Basket.findById(bid); + if (!b) res.status(404).send("Basket not found."); + const newItemList = b?.items.filter( + (id) => id.toString() != itemToRemove._id + ); + + if (newItemList?.length === b?.items.length) + res.status(404).send("Item not found"); + + const partial: Partial = { items: newItemList }; + const updatedBasket = await Basket.findByIdAndUpdate(bid, partial, { + new: true, + runValidators: true, + }).lean(); + + res.status(200).send(updatedBasket); + } catch (error) { + console.error("Error deleting the Basket:", error); + res.status(500).send("Internal Server Error"); + } +}); + router.delete("/:id", async (req: Request, res: Response) => { connectDB(); const { id } = req.params; diff --git a/frontend/lib/deletes.tsx b/frontend/lib/deletes.tsx index 9d679ee..74684f1 100644 --- a/frontend/lib/deletes.tsx +++ b/frontend/lib/deletes.tsx @@ -35,7 +35,7 @@ export const handleDeleteBasket = async (basketId: string) => { export const removeFriendFromUserByFriendId = async ( friendId: string, - userId: string, + userId: string ) => { try { const response = await fetch( @@ -46,7 +46,7 @@ export const removeFriendFromUserByFriendId = async ( "Content-Type": "application/json", }, body: JSON.stringify({ friendId: friendId }), - }, + } ); if (!response.ok) { throw new Error(`Error: ${response.statusText}`); @@ -57,9 +57,46 @@ export const removeFriendFromUserByFriendId = async ( } }; +export const deleteItem = async ( + item: IItem, + basket: string | undefined = undefined +) => { + try { + if (!item.basket && !basket) + throw Error("Item missing basket and basket unsupplied."); + fetch( + `${vite_backend_url}/baskets/${item.basket ? item.basket : basket}/removeitem`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(item._id), + } + ) + .then((res) => { + if (res.status === 200) { + return fetch(`${vite_backend_url}/items/${item._id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + } + }) + .catch((err) => { + throw err; + }); + } catch (error) { + console.log(`Error deleting ${item.name}: ${error}`); + } +}; + export const removeItemFromBasketAndDelete = async ( baskets: IBasket[], - item: IItem, + item: IItem ) => { try { baskets.forEach(async (basket) => { @@ -87,7 +124,7 @@ export const removeItemFromBasketAndDelete = async ( } catch (error) { console.error( "There was an error removing the item from the basket", - error, + error ); } }; From 733fac39d9dde3034347d6e5cec0cdbdd721a54a Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 16:52:29 -0700 Subject: [PATCH 09/27] Delete item --- backend/routes/basketRoutes.ts | 1 - backend/routes/itemRoutes.ts | 2 -- frontend/lib/deletes.tsx | 3 +-- frontend/src/components/BasketItem.tsx | 23 ++++++++++++++++++++++- frontend/src/components/CompactItem.tsx | 2 ++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/backend/routes/basketRoutes.ts b/backend/routes/basketRoutes.ts index d998d81..ea5e3ee 100644 --- a/backend/routes/basketRoutes.ts +++ b/backend/routes/basketRoutes.ts @@ -105,7 +105,6 @@ router.patch("/:id", async (req: Request, res: Response) => { router.patch("/:bid/removeitem", async (req: Request, res: Response) => { connectDB(); - console.log("running delete"); const { bid } = req.params; const itemToRemove = req.body; diff --git a/backend/routes/itemRoutes.ts b/backend/routes/itemRoutes.ts index a6a29ee..a9621e5 100644 --- a/backend/routes/itemRoutes.ts +++ b/backend/routes/itemRoutes.ts @@ -24,8 +24,6 @@ router.get("/:itemid", async (req: Request, res: Response) => { connectDB(); try { - console.log("Here"); - // Use findById correctly with the id parameter from the request const itemById = await Item.findById(req.params.itemid); diff --git a/frontend/lib/deletes.tsx b/frontend/lib/deletes.tsx index 74684f1..0dfbfde 100644 --- a/frontend/lib/deletes.tsx +++ b/frontend/lib/deletes.tsx @@ -70,9 +70,8 @@ export const deleteItem = async ( method: "PATCH", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${token}`, }, - body: JSON.stringify(item._id), + body: JSON.stringify({ _id: item._id }), } ) .then((res) => { diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index e053973..d3eeeab 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -1,10 +1,19 @@ -import { Text, Icon, SkeletonText, Box, Flex } from "@chakra-ui/react"; +import { + Text, + Icon, + SkeletonText, + Box, + Flex, + IconButton, +} from "@chakra-ui/react"; import { useEffect, useState } from "react"; import { VscEye, VscEyeClosed } from "react-icons/vsc"; import "../styles/BasketItem.css"; import { fetchItem } from "../../lib/fetches"; import { IItem } from "../../../backend/models/itemSchema"; import EditItem from "./EditItem"; +import { DeleteIcon } from "@chakra-ui/icons"; +import { deleteItem } from "../../lib/deletes"; interface Props { itemId: string; @@ -41,6 +50,12 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { }); }, [itemId]); + const removeItem = async (item: IItem) => { + console.log(item); + deleteItem(item).catch((err) => console.log(err)); + //window.location.reload(); + }; + if (!basketMemberView && item?.isPrivate) { return; } @@ -106,6 +121,12 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { {item?.quantity} + } + colorScheme="red" + onClick={() => removeItem(item)} + /> )} diff --git a/frontend/src/components/CompactItem.tsx b/frontend/src/components/CompactItem.tsx index d25a7c9..34f557a 100644 --- a/frontend/src/components/CompactItem.tsx +++ b/frontend/src/components/CompactItem.tsx @@ -13,6 +13,8 @@ import { import { EditIcon, SearchIcon } from "@chakra-ui/icons"; import { IItem } from "../../../backend/models/itemSchema"; +// DEPRECATED + UNUSED + const CompactItem = ({ item }: { item: IItem }) => { // Note: Colors not added yet, just basic structure return ( From bc728615931ec5d119b1b7ab089d17a00f0e7d5d Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 16:53:08 -0700 Subject: [PATCH 10/27] Fixed EditGroup error --- frontend/src/components/EditGroup.tsx | 20 ++++++++------------ frontend/src/components/NewBasketOptions.tsx | 3 +-- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/EditGroup.tsx b/frontend/src/components/EditGroup.tsx index a55bd1a..8d246ca 100644 --- a/frontend/src/components/EditGroup.tsx +++ b/frontend/src/components/EditGroup.tsx @@ -48,20 +48,16 @@ const Editgroup: React.FC = ({ GroupId }) => { const fetchgroupData = async () => { try { const response = await fetchGroupById(GroupId); - if (response.ok) { - const data = await response.json(); + if (response) setgroupData({ - GroupId: data.GroupId, - groupName: data.groupName, - groupDesc: data.description, - groupPub: data.privateGroup, + GroupId: response._id, + groupName: response.groupName, + groupDesc: response.description, + groupPub: response.privateGroup, }); - setEditedName(data.name); - setEditedDesc(data.description); - setEditedPub(data.privateGroup); - } else { - console.error(`Failed to fetch user data for group ${GroupId}`); - } + setEditedName(response.groupName); + setEditedDesc(response.description); + setEditedPub(response.privateGroup); } catch (error) { console.error("Error fetching user data:", error); } diff --git a/frontend/src/components/NewBasketOptions.tsx b/frontend/src/components/NewBasketOptions.tsx index 3057ad7..7470e41 100644 --- a/frontend/src/components/NewBasketOptions.tsx +++ b/frontend/src/components/NewBasketOptions.tsx @@ -85,7 +85,6 @@ const CreateGroup = ({ postBasket }: CreateProps) => { const handleChange = (event: FormEvent) => { const { name, value } = event.currentTarget; - console.log("Edited ", name, " value", value); if (name === "name") { setBasket({ ...basket, name: value }); } else { @@ -96,7 +95,7 @@ const CreateGroup = ({ postBasket }: CreateProps) => { const handleSubmit = () => { postBasket( basket.name, - basket.description === "" ? "No description given" : basket.description, + basket.description === "" ? "No description given" : basket.description ); setBasket({ name: "", description: "" }); }; From 3d6df98766b6d8d07a39a968ae2e1a71a4f7a685 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 16:53:27 -0700 Subject: [PATCH 11/27] Force reload is back!!! --- frontend/src/components/BasketItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index d3eeeab..f28a3fe 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -53,7 +53,7 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { const removeItem = async (item: IItem) => { console.log(item); deleteItem(item).catch((err) => console.log(err)); - //window.location.reload(); + window.location.reload(); }; if (!basketMemberView && item?.isPrivate) { From 8c5114e90504784af473ebb57ffec8565cd69ca0 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 16:55:03 -0700 Subject: [PATCH 12/27] Removed quantity --- frontend/src/components/BasketItem.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index f28a3fe..2008335 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -118,9 +118,6 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { {item.isPrivate ? "Not v" : "V"}iewable by other group members - - {item?.quantity} - } From 11f5e19faa9f26f7be6e4e0b83605dc8e4e74054 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 17:50:53 -0700 Subject: [PATCH 13/27] Cleaned up old artifacts --- frontend/src/App.tsx | 20 -------------------- frontend/src/styles/moveLetters.css | 1 + 2 files changed, 1 insertion(+), 20 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a01edf6..66636ea 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -6,13 +6,9 @@ import SignupPage from "./pages/SignupPage"; import ItemsPage from "./pages/ItemsPage"; import NavbarSignedOut from "./components/NavbarSignedOut"; import NavbarSignedIn from "./components/NavbarSignedIn"; -import Friends_List from "./components/Friends_List_Component"; import ProfilePage from "./pages/ProfilePage"; import GroupPage from "./pages/MyGroupsPage"; import IndividualGroupPage from "./pages/IndividualGroupPage"; -import EditItem from "./components/EditItem"; -import EditGroup from "./components/EditGroup"; -import EditBasket from "./components/EditBasket"; import { IUser } from "../../backend/models/userSchema"; import MoveLetters from "./components/moveLetters"; import theme from "./theme"; @@ -95,10 +91,6 @@ function App() { path="/login" element={} /> - } - /> } /> - } - /> - } - /> - } - /> diff --git a/frontend/src/styles/moveLetters.css b/frontend/src/styles/moveLetters.css index 2f8c37e..91289f7 100644 --- a/frontend/src/styles/moveLetters.css +++ b/frontend/src/styles/moveLetters.css @@ -3,6 +3,7 @@ justify-content: center; align-items: center; height: 100vh; + width: 100vw; position: relative; /* This will serve as a stacking context */ z-index: 0; /* Base layer */ } From 89af52839261b2f240ed72f743b1bc3a7c2597cd Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 17:51:10 -0700 Subject: [PATCH 14/27] Updated individual group/basket owner view --- frontend/src/components/Basket.tsx | 40 ++++++++++++++-------- frontend/src/components/BasketItem.tsx | 2 +- frontend/src/pages/IndividualGroupPage.tsx | 15 ++++++-- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index 34d9896..2cc528d 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -76,13 +76,17 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { // we designed our database, it was nearly impossible to fix this design flaw by the time we realized (we just didn't have enough time // to change the rest of our project since it was so late in the train). We acknowledge that this is a privacy problem and we would have // liked to fix it but because of time constraints we were unable to. - const isMemberOfBasket = LoggedInUser - ? basketObj - ? basketObj.members - ? basketObj.members.includes(LoggedInUser?._id) - : false - : false - : false; + const isMemberOfBasket = + LoggedInUser && + basketObj && + basketObj.members && + basketObj.members.includes(LoggedInUser?._id); + + const isOwnerOfBasket = + LoggedInUser && + basketObj && + basketObj.members && + basketObj.members[0] === LoggedInUser?._id; return ( { base: "column", }} > - - + {isOwnerOfBasket ? ( + + ) : ( + <> + )} + {isMemberOfBasket ? ( + + ) : ( + <> + )} @@ -186,7 +198,7 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { return ( ); diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index 2008335..45cc335 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -65,7 +65,7 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { {loading ? ( - + diff --git a/frontend/src/pages/IndividualGroupPage.tsx b/frontend/src/pages/IndividualGroupPage.tsx index b254f3d..62618bc 100644 --- a/frontend/src/pages/IndividualGroupPage.tsx +++ b/frontend/src/pages/IndividualGroupPage.tsx @@ -29,6 +29,7 @@ import Editgroup from "../components/EditGroup"; import NewBasketOptions from "../components/NewBasketOptions"; import SendInviteToGroup from "../components/SendInvite"; import { fetchUserWithString } from "../../lib/fetches"; +import RemoveFromGroup from "../components/RemoveFromGroup"; const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; @@ -44,6 +45,7 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { const [members, setMembers] = useState([]); const [friends, setFriends] = useState([]); const navigate = useNavigate(); + const fetchFriends = async (friendIds: string[]) => { try { const fetchedFriends = await Promise.all( @@ -124,6 +126,8 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { } }, [loading]); + const isGroupOwner = members && members[0]?._id === LoggedInUser?._id; + return ( = ({ LoggedInUser }) => { groupId={String(groupId)} friends={friends} members={members ?? []} - > + /> } /> = ({ LoggedInUser }) => { {group.groupName} - {groupId ? : <>} + {groupId && isGroupOwner ? ( + <> + + + + ) : ( + <> + )} From 697af2c824cf9be8d399a438c75a32219e1a5f58 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 17:51:23 -0700 Subject: [PATCH 15/27] Avatar rendering --- frontend/src/components/CompactGroup.tsx | 30 ++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/CompactGroup.tsx b/frontend/src/components/CompactGroup.tsx index 89c8fea..251f224 100644 --- a/frontend/src/components/CompactGroup.tsx +++ b/frontend/src/components/CompactGroup.tsx @@ -2,6 +2,8 @@ import { Box, VStack, Text, Avatar, HStack } from "@chakra-ui/react"; import { IGroup } from "../../../backend/models/groupSchema"; import "../styles/CompactGroup.css"; import ConstrainedText from "./ConstrainedText"; +import { useEffect, useState } from "react"; +import { fetchMembers } from "../../lib/fetches"; interface Props { group: IGroup; @@ -16,6 +18,20 @@ const CompactGroupV1 = ({ height, corners = [false, false, false, false], }: Props) => { + const [memberNames, setMemberNames] = useState([]); + + useEffect(() => { + fetchMembers(group.members) + .then((res) => { + let temp = []; // extract just the usernames from response + for (let i = 0; i < res.length; i++) { + temp.push(res[i].username); + } + setMemberNames(temp); + }) + .catch(() => console.log("Error loading member names")); + }, [group]); + return ( - + 0 ? memberNames[0] : ""} + /> Created {new Date(group.created).toDateString()} - {group.members.length > 1 ? ( + {memberNames?.length > 1 ? ( - - {group.members.length > 2 ? : undefined} + + {group.members.length > 2 ? ( + + ) : undefined} {group.members.length > 3 ? ( Date: Sat, 1 Jun 2024 21:41:26 -0700 Subject: [PATCH 16/27] Fixed basket overflow scrolling, changed view permissions for adding new items --- frontend/src/components/Basket.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index 2cc528d..b0af9b4 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -91,7 +91,9 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { return ( { > Basket Items - + From 5cef6df89acb3947bf0655adb3dd743bf3a4e7e9 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 21:41:35 -0700 Subject: [PATCH 17/27] Changed viewing permissions for editing/deleting items --- frontend/src/components/BasketItem.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index 45cc335..a21fd1c 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -93,7 +93,10 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { {item?.notes} - + { } colorScheme="red" From 4898b068d617b53a4334cb530c18f6a60f510d80 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 21:42:12 -0700 Subject: [PATCH 18/27] Updated editItem to adhere to privacy standards according to the EU decree number 10765. --- frontend/src/components/EditItem.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/EditItem.tsx b/frontend/src/components/EditItem.tsx index c994fe5..f9eef95 100644 --- a/frontend/src/components/EditItem.tsx +++ b/frontend/src/components/EditItem.tsx @@ -35,9 +35,10 @@ import { editItem } from "../../lib/edits"; interface Props { itemId: string; + editable: boolean; } -const EditItem: React.FC = ({ itemId }) => { +const EditItem: React.FC = ({ itemId, editable }) => { // Note: Colors not added yet, just basic structure const [isEditing, setIsEditing] = useState(false); const [editedName, setEditedName] = useState(""); @@ -374,6 +375,7 @@ const EditItem: React.FC = ({ itemId }) => { mt={4} ml="auto" onClick={() => setIsEditing(true)} + display={editable ? "flex" : "none"} > Edit Item From 1d25bee44eee7c7b3d69b6ee0dd56fa7389ccd2b Mon Sep 17 00:00:00 2001 From: jhcp Date: Sat, 1 Jun 2024 21:42:38 -0700 Subject: [PATCH 19/27] Opinionated UI design changes --- frontend/src/components/NewBasketOptions.tsx | 3 +-- frontend/src/components/NewItemOptions.tsx | 12 +++++++----- frontend/src/pages/IndividualGroupPage.tsx | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/frontend/src/components/NewBasketOptions.tsx b/frontend/src/components/NewBasketOptions.tsx index 7470e41..cb9f1f7 100644 --- a/frontend/src/components/NewBasketOptions.tsx +++ b/frontend/src/components/NewBasketOptions.tsx @@ -119,14 +119,13 @@ const CreateGroup = ({ postBasket }: CreateProps) => { borderRadius="30px" borderColor="var(--col-secondary)" borderWidth="3px" - width="100px" height="30px" marginRight="2px" fontWeight="300" fontSize="14px" letterSpacing="1px" > - CREATE + ADD NEW { const createItem = async ( name: string, @@ -40,7 +42,7 @@ const NewItemOptions = ({ type: string, notes: string, price: number, - quantity: number, + quantity: number ) => { const res = await fetchBasket(basket); @@ -74,7 +76,7 @@ const NewItemOptions = ({ }; return ( - + Add Item @@ -91,7 +93,7 @@ interface CreateProps { type: string, notes: string, price: number, - quantity: number, + quantity: number ) => void; } @@ -126,7 +128,7 @@ const CreateItem = ({ postItem }: CreateProps) => { item.type, item.notes === "" ? "No description given" : item.notes, item.price, - item.quantity, + item.quantity ); setItem({ name: "", @@ -147,7 +149,7 @@ const CreateItem = ({ postItem }: CreateProps) => { const handleNumberInputChangeQuantity = ( valueAsString: string, - valueAsNumber: number, + valueAsNumber: number ) => { console.log(valueAsString); setItem((prevItem) => ({ ...prevItem, quantity: valueAsNumber })); diff --git a/frontend/src/pages/IndividualGroupPage.tsx b/frontend/src/pages/IndividualGroupPage.tsx index 62618bc..c409d8c 100644 --- a/frontend/src/pages/IndividualGroupPage.tsx +++ b/frontend/src/pages/IndividualGroupPage.tsx @@ -179,7 +179,7 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { flexDirection="column" padding="20px" flex="1" - overflowY="scroll" + overflowY="auto" alignItems="center" > {loading ? ( @@ -280,14 +280,16 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { - Baskets - + + Baskets + + - + {groupBaskets && members ? ( groupBaskets.map((basket) => ( Date: Sat, 1 Jun 2024 22:01:21 -0700 Subject: [PATCH 20/27] Disabled adding friends that already exist in basket --- frontend/src/components/AddFriendToBasket.tsx | 18 ++++++++++++------ frontend/src/components/Basket.tsx | 3 ++- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/AddFriendToBasket.tsx b/frontend/src/components/AddFriendToBasket.tsx index 43f656e..296d740 100644 --- a/frontend/src/components/AddFriendToBasket.tsx +++ b/frontend/src/components/AddFriendToBasket.tsx @@ -13,30 +13,33 @@ import { import { IUser } from "../../../backend/models/userSchema"; import { fetchBasket } from "../../lib/fetches"; import { editBasket } from "../../lib/edits"; +import { ObjectId } from "mongoose"; interface Props { basketId: string; - memberid: IUser[]; + groupMembers: IUser[]; + basketMemberIds: ObjectId[]; currentUserId: string | undefined; // Add a prop for the current user's ID } // Uses member ids that are passed in from basket.tsx const AddFriendToBasket: React.FC = ({ basketId, - memberid, + groupMembers, + basketMemberIds, currentUserId, }) => { // Initialize the members state with the filtered memberid prop const [members, setMembers] = useState(() => - memberid.filter((member) => member._id.toString() !== currentUserId), + groupMembers.filter((member) => member._id.toString() !== currentUserId) ); useEffect(() => { // This effect runs when memberid prop changes setMembers( - memberid.filter((member) => member._id.toString() !== currentUserId), + groupMembers.filter((member) => member._id.toString() !== currentUserId) ); - }, [memberid, currentUserId]); + }, [groupMembers, currentUserId]); const AddToBasket = async (basketId: string, friendId: string) => { try { @@ -106,8 +109,11 @@ const AddFriendToBasket: React.FC = ({ ))} diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index b0af9b4..05ecc3a 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -164,7 +164,8 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { {isOwnerOfBasket ? ( ) : ( From 7c588ea970cade53993f404d7938194faa0619c2 Mon Sep 17 00:00:00 2001 From: jhcp Date: Sun, 2 Jun 2024 14:44:37 -0700 Subject: [PATCH 21/27] Merged from main and deleted half my changes so no remove item and no remove from group --- .github/workflows/main_gather-app-inv.yml | 72 ------ backend/auth.ts | 4 +- backend/index.ts | 10 +- backend/package.json | 6 +- backend/routes/basketRoutes.ts | 76 +++--- backend/routes/groupRoutes.ts | 71 +++--- backend/routes/itemRoutes.ts | 69 ++--- backend/routes/userRoutes.ts | 110 ++++---- backend/tsconfig.json | 18 ++ frontend/lib/deletes.tsx | 237 +++++++++++++++--- frontend/lib/edits.tsx | 88 ++++++- frontend/lib/fetches.tsx | 118 ++++++--- frontend/lib/posts.tsx | 2 + .../public/{HomePage2.jpg => HomePage.jpg} | Bin frontend/src/App.tsx | 25 +- frontend/src/components/Basket.tsx | 14 +- frontend/src/components/BasketItem.tsx | 9 +- frontend/src/components/EditBasket.tsx | 30 ++- frontend/src/components/EditGroup.tsx | 48 +++- .../src/components/Friends_List_Component.tsx | 22 +- frontend/src/components/NewGroupOptions.tsx | 6 +- frontend/src/components/RemoveFromGroup.tsx | 82 ++++++ frontend/src/pages/IndividualGroupPage.tsx | 81 +++--- frontend/src/pages/ItemsPage.tsx | 2 + frontend/src/pages/LoginPage.tsx | 2 + frontend/src/pages/MyGroupsPage.tsx | 14 +- frontend/src/pages/ProfilePage.tsx | 11 +- frontend/src/styles/moveLetters.css | 2 +- package.json | 1 + 29 files changed, 840 insertions(+), 390 deletions(-) delete mode 100644 .github/workflows/main_gather-app-inv.yml create mode 100644 backend/tsconfig.json rename frontend/public/{HomePage2.jpg => HomePage.jpg} (100%) create mode 100644 frontend/src/components/RemoveFromGroup.tsx diff --git a/.github/workflows/main_gather-app-inv.yml b/.github/workflows/main_gather-app-inv.yml deleted file mode 100644 index 33bd182..0000000 --- a/.github/workflows/main_gather-app-inv.yml +++ /dev/null @@ -1,72 +0,0 @@ -# Docs for the Azure Web Apps Deploy action: https://github.com/Azure/webapps-deploy -# More GitHub Actions for Azure: https://github.com/Azure/actions - -name: Build and deploy Node.js app to Azure Web App - gather-app-inv - -on: - push: - branches: - - main - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Set up Node.js version - uses: actions/setup-node@v3 - with: - node-version: "20.x" - - - name: npm install, build, and test - run: | - npm install --workspaces=false --legacy-peer-deps - npm run -w backend build --if-present - npm run -w backend test --if-present - working-directory: backend - - - name: Zip artifact for deployment - run: zip release.zip ./* -r - working-directory: backend - - - name: Upload artifact for deployment job - uses: actions/upload-artifact@v3 - with: - name: node-app - path: backend/release.zip - - deploy: - runs-on: ubuntu-latest - needs: build - environment: - name: "Production" - url: ${{ steps.deploy-to-webapp.outputs.webapp-url }} - permissions: - id-token: write #This is required for requesting the JWT - - steps: - - name: Download artifact from build job - uses: actions/download-artifact@v3 - with: - name: node-app - - - name: Unzip artifact for deployment - run: unzip release.zip - - - name: Login to Azure - uses: azure/login@v1 - with: - client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_7234F5539E704699B646CABB541CBF19 }} - tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_EDB7C80261714FECB4D9646EC7145443 }} - subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_6CD2C32FCA004CC99FCD4833293EA065 }} - - - name: "Deploy to Azure Web App" - id: deploy-to-webapp - uses: azure/webapps-deploy@v2 - with: - app-name: "gather-app-inv" - slot-name: "Production" - package: . diff --git a/backend/auth.ts b/backend/auth.ts index fec0e89..49ff839 100644 --- a/backend/auth.ts +++ b/backend/auth.ts @@ -1,9 +1,9 @@ import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; -import User, { IUser } from "./models/userSchema"; +import User, { IUser } from "./models/userSchema.js"; import dotenv from "dotenv"; import { Request, Response } from "express"; -import connectDB from "./connection"; +import connectDB from "./connection.js"; dotenv.config(); diff --git a/backend/index.ts b/backend/index.ts index 1506d9d..a62a187 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -1,9 +1,9 @@ import express, { Express, Request, Response, NextFunction } from "express"; -import { userEndpoints } from "./routes/userRoutes"; -import { groupEndpoints } from "./routes/groupRoutes"; -import { basketEndpoints } from "./routes/basketRoutes"; -import { itemEndpoints } from "./routes/itemRoutes"; -import { loginUser } from "./auth"; +import { userEndpoints } from "./routes/userRoutes.js"; +import { groupEndpoints } from "./routes/groupRoutes.js"; +import { basketEndpoints } from "./routes/basketRoutes.js"; +import { itemEndpoints } from "./routes/itemRoutes.js"; +import { loginUser } from "./auth.js"; import jwt from "jsonwebtoken"; const app: Express = express(); diff --git a/backend/package.json b/backend/package.json index 739204e..0bae24e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,10 +2,12 @@ "name": "backend", "version": "1.0.0", "description": "", - "main": "backend.ts", + "main": "index.ts", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 0", - "dev": "nodemon index.ts", + "dev": "node --loader ts-node/esm ./index.ts", + "start": "node --loader ts-node/esm ./index.ts", "lint": "npx eslint --report-unused-disable-directives --max-warnings 0 && npx prettier --check ." }, "author": "", diff --git a/backend/routes/basketRoutes.ts b/backend/routes/basketRoutes.ts index ea5e3ee..ef75b6f 100644 --- a/backend/routes/basketRoutes.ts +++ b/backend/routes/basketRoutes.ts @@ -1,13 +1,12 @@ import express from "express"; import { Request, Response } from "express"; -import Basket, { IBasket } from "../models/basketSchema"; -import connectDB from "../connection"; -import { ObjectId } from "mongoose"; -import { IItem } from "../models/itemSchema"; +import Basket, { IBasket } from "../models/basketSchema.js"; +import connectDB from "../connection.js"; +import { authenticateUser } from "../auth.js"; const router = express.Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { const baskets = await Basket.find({}); @@ -21,39 +20,43 @@ router.get("/", async (req: Request, res: Response) => { } }); -router.get("/:basketid", async (req: Request, res: Response) => { - // Ensure the database connection - connectDB(); +router.get( + "/:basketid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - try { - // Use findById correctly with the id parameter from the request - const basketById = await Basket.findById(req.params.basketid); - - // Check if basket is null or undefined - if (!basketById) { - // If not found by ObjectId, try to find by basketName - const basketsByName = await Basket.find({ - basketName: req.params.basketid, - }); - - if (!basketsByName.length) { - return res.status(404).send("No baskets found"); // Use return to exit the function after sending the response + try { + // Use findById correctly with the id parameter from the request + const basketById = await Basket.findById(req.params.basketid); + + // Check if basket is null or undefined + if (!basketById) { + // If not found by ObjectId, try to find by basketName + const basketsByName = await Basket.find({ + basketName: req.params.basketid, + }); + + if (!basketsByName.length) { + return res.status(404).send("No baskets found"); // Use return to exit the function after sending the response + } + + // Send the found baskets + return res.send(basketsByName); } - // Send the found baskets - return res.send(basketsByName); + // Send the found basket by ObjectId + res.send(basketById); + console.log("Sent Basket:", basketById); + } catch (error) { + console.error("Error fetching basket:", error); // Log the error for debugging + res.status(500).send("Internal Server Error"); } - - // Send the found basket by ObjectId - res.send(basketById); - console.log("Sent Basket:", basketById); - } catch (error) { - console.error("Error fetching basket:", error); // Log the error for debugging - res.status(500).send("Internal Server Error"); } -}); +); -router.post("/", async (req: Request, res: Response) => { +router.post("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { console.log("Creating a new basket with data:", req.body); @@ -80,7 +83,7 @@ router.post("/", async (req: Request, res: Response) => { } }); -router.patch("/:id", async (req: Request, res: Response) => { +router.patch("/:id", authenticateUser, async (req: Request, res: Response) => { // Get basket ID from URL const { id } = req.params; const updatedData: Partial = req.body; // Not a full update, only partial @@ -104,20 +107,17 @@ router.patch("/:id", async (req: Request, res: Response) => { }); router.patch("/:bid/removeitem", async (req: Request, res: Response) => { - connectDB(); + console.log("running delete"); const { bid } = req.params; const itemToRemove = req.body; - try { const b: IBasket | null = await Basket.findById(bid); if (!b) res.status(404).send("Basket not found."); const newItemList = b?.items.filter( (id) => id.toString() != itemToRemove._id ); - if (newItemList?.length === b?.items.length) res.status(404).send("Item not found"); - const partial: Partial = { items: newItemList }; const updatedBasket = await Basket.findByIdAndUpdate(bid, partial, { new: true, @@ -131,7 +131,7 @@ router.patch("/:bid/removeitem", async (req: Request, res: Response) => { } }); -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); const { id } = req.params; try { diff --git a/backend/routes/groupRoutes.ts b/backend/routes/groupRoutes.ts index f148aae..9c08d8b 100644 --- a/backend/routes/groupRoutes.ts +++ b/backend/routes/groupRoutes.ts @@ -1,11 +1,12 @@ import express from "express"; import { Request, Response } from "express"; -import Group, { IGroup } from "../models/groupSchema"; -import connectDB from "../connection"; +import Group, { IGroup } from "../models/groupSchema.js"; +import { authenticateUser } from "../auth.js"; +import connectDB from "../connection.js"; const router = express.Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { const users = await Group.find({}); @@ -19,42 +20,48 @@ router.get("/", async (req: Request, res: Response) => { } }); -router.get("/:groupid", async (req: Request, res: Response) => { - // Ensure the database connection - connectDB(); - - try { - // Use findById correctly with the id parameter from the request - const groupById = await Group.findById(req.params.groupid); - - // Check if group is null or undefined - if (!groupById) { - return res.status(404).send("No group found"); // Use return to exit the function after sending the response - } +router.get( + "/:groupid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - // Send the found user - res.send(groupById); - console.log("Sent Group:", groupById); - } catch (error) { - console.log("Now trying to find by GroupName"); try { - const groupsByName = await Group.find({ groupName: req.params.groupid }); - console.log(groupsByName); - if (!groupsByName) { - return res.status(404).send("No groups found"); // Use return to exit the function after sending the response + // Use findById correctly with the id parameter from the request + const groupById = await Group.findById(req.params.groupid); + + // Check if group is null or undefined + if (!groupById) { + return res.status(404).send("No group found"); // Use return to exit the function after sending the response } // Send the found user - res.send(groupsByName); - console.log("Sent Groups", groupsByName); + res.send(groupById); + console.log("Sent Group:", groupById); } catch (error) { - console.error("Error fetching group:", error); // Log the error for debugging - res.status(500).send("Internal Server Error"); + console.log("Now trying to find by GroupName"); + try { + const groupsByName = await Group.find({ + groupName: req.params.groupid, + }); + console.log(groupsByName); + if (!groupsByName) { + return res.status(404).send("No groups found"); // Use return to exit the function after sending the response + } + + // Send the found user + res.send(groupsByName); + console.log("Sent Groups", groupsByName); + } catch (error) { + console.error("Error fetching group:", error); // Log the error for debugging + res.status(500).send("Internal Server Error"); + } } } -}); +); -router.post("/", async (req: Request, res: Response) => { +router.post("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { console.log("Creating a new group with data:", req.body); @@ -85,7 +92,7 @@ router.post("/", async (req: Request, res: Response) => { } }); -router.patch("/:id", async (req: Request, res: Response) => { +router.patch("/:id", authenticateUser, async (req: Request, res: Response) => { // Get user ID from URL const { id } = req.params; const updatedData: Partial = req.body; //Not a full update only partial @@ -108,7 +115,7 @@ router.patch("/:id", async (req: Request, res: Response) => { } }); -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); const { id } = req.params; try { diff --git a/backend/routes/itemRoutes.ts b/backend/routes/itemRoutes.ts index a9621e5..ef12b86 100644 --- a/backend/routes/itemRoutes.ts +++ b/backend/routes/itemRoutes.ts @@ -1,11 +1,12 @@ import express from "express"; import { Request, Response } from "express"; -import Item, { IItem } from "../models/itemSchema"; -import connectDB from "../connection"; +import Item, { IItem } from "../models/itemSchema.js"; +import { authenticateUser } from "../auth.js"; +import connectDB from "../connection.js"; const router = express.Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { const users = await Item.find({}); @@ -19,41 +20,45 @@ router.get("/", async (req: Request, res: Response) => { } }); -router.get("/:itemid", async (req: Request, res: Response) => { - // Ensure the database connection - connectDB(); - - try { - // Use findById correctly with the id parameter from the request - const itemById = await Item.findById(req.params.itemid); +router.get( + "/:itemid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - // Check if item is null or undefined - if (!itemById) { - return res.status(404).send("No item found"); // Use return to exit the function after sending the response - } - // Send the found item - res.send(itemById); - console.log("Sent item"); - } catch (error) { - console.log("Now trying to find by Name"); try { - const itemsByName = await Item.find({ name: req.params.itemid }); - console.log(itemsByName); - if (!itemsByName) { - return res.status(404).send("No items found"); // Use return to exit the function after sending the response - } + // Use findById correctly with the id parameter from the request + const itemById = await Item.findById(req.params.itemid); + // Check if item is null or undefined + if (!itemById) { + return res.status(404).send("No item found"); // Use return to exit the function after sending the response + } // Send the found item - res.send(itemsByName); - console.log("Sent items"); + res.send(itemById); + console.log("Sent item"); } catch (error) { - console.error("Error fetching group:", error); // Log the error for debugging - res.status(500).send("Internal Server Error"); + console.log("Now trying to find by Name"); + try { + const itemsByName = await Item.find({ name: req.params.itemid }); + console.log(itemsByName); + if (!itemsByName) { + return res.status(404).send("No items found"); // Use return to exit the function after sending the response + } + + // Send the found item + res.send(itemsByName); + console.log("Sent items"); + } catch (error) { + console.error("Error fetching group:", error); // Log the error for debugging + res.status(500).send("Internal Server Error"); + } } } -}); +); -router.post("/", async (req: Request, res: Response) => { +router.post("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { console.log("Creating a new item with data:", req.body); @@ -91,7 +96,7 @@ router.post("/", async (req: Request, res: Response) => { } }); -router.patch("/:id", async (req: Request, res: Response) => { +router.patch("/:id", authenticateUser, async (req: Request, res: Response) => { // Get user ID from URL const { id } = req.params; const updatedData: Partial = req.body; //Not a full update only partial @@ -114,7 +119,7 @@ router.patch("/:id", async (req: Request, res: Response) => { } }); -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); const { id } = req.params; try { diff --git a/backend/routes/userRoutes.ts b/backend/routes/userRoutes.ts index 4e898f1..c6c3f30 100644 --- a/backend/routes/userRoutes.ts +++ b/backend/routes/userRoutes.ts @@ -1,13 +1,13 @@ import express from "express"; import { Request, Response } from "express"; -import User, { IUser } from "../models/userSchema"; -import connectDB from "../connection"; -import { authenticateUser, generateAccessToken } from "../auth"; +import User, { IUser } from "../models/userSchema.js"; +import connectDB from "../connection.js"; +import { authenticateUser, generateAccessToken } from "../auth.js"; import bcrypt from "bcrypt"; import mongoose from "mongoose"; const router = express.Router(); -router.get("/", async (req: Request, res: Response) => { +router.get("/", authenticateUser, async (req: Request, res: Response) => { connectDB(); try { @@ -22,26 +22,16 @@ router.get("/", async (req: Request, res: Response) => { } }); -router.get("/:userid", async (req: Request, res: Response) => { - // Ensure the database connection - connectDB(); - - try { - // Use findById correctly with the id parameter from the request - const user = await User.findById(req.params.userid); - - // Check if group is null or undefined - if (!user) { - return res.status(404).send("No users found"); // Use return to exit the function after sending the response - } +router.get( + "/:userid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - // Send the found user - res.send(user); - console.log("Sent user", user); - } catch (error) { try { // Use findById correctly with the id parameter from the request - const user = await User.findOne({ username: req.params.userid }); + const user = await User.findById(req.params.userid); // Check if group is null or undefined if (!user) { @@ -50,13 +40,27 @@ router.get("/:userid", async (req: Request, res: Response) => { // Send the found user res.send(user); - console.log("Sent user"); + console.log("Sent user", user); } catch (error) { - console.error("Error fetching user:", error); // Log the error for debugging - res.status(500).send("Internal Server Error"); + try { + // Use findById correctly with the id parameter from the request + const user = await User.findOne({ username: req.params.userid }); + + // Check if group is null or undefined + if (!user) { + return res.status(404).send("No users found"); // Use return to exit the function after sending the response + } + + // Send the found user + res.send(user); + console.log("Sent user"); + } catch (error) { + console.error("Error fetching user:", error); // Log the error for debugging + res.status(500).send("Internal Server Error"); + } } - } -}); + }, +); router.post("/", async (req: Request, res: Response) => { connectDB(); @@ -108,7 +112,7 @@ router.post("/", async (req: Request, res: Response) => { } }); -router.patch("/:id", async (req: Request, res: Response) => { +router.patch("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); // Get user ID from URL const { id } = req.params; @@ -131,7 +135,7 @@ router.patch("/:id", async (req: Request, res: Response) => { } }); -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); const { id } = req.params; @@ -149,29 +153,33 @@ router.delete("/:id", async (req: Request, res: Response) => { } }); -router.delete("/:id/remove-friend", async (req: Request, res: Response) => { - connectDB(); - const userId = req.params.id; - const { friendId } = req.body; // Expecting friendId in the request body - console.log(friendId); - try { - const user = await User.findById(userId); - console.log(user); - if (user) { - // Remove the friend's ObjectId from the user's friends array - user.friends = user.friends.filter( - (friend: mongoose.Types.ObjectId) => !friend.equals(friendId), - ); - await user.save(); - - res.status(200).send({ message: "Friend removed successfully" }); - } else { - res.status(404).send({ message: "User not found" }); +router.delete( + "/:id/remove-friend", + authenticateUser, + async (req: Request, res: Response) => { + connectDB(); + const userId = req.params.id; + const { friendId } = req.body; // Expecting friendId in the request body + console.log(friendId); + try { + const user = await User.findById(userId); + console.log(user); + if (user) { + // Remove the friend's ObjectId from the user's friends array + user.friends = user.friends.filter( + (friend: mongoose.Types.ObjectId) => !friend.equals(friendId), + ); + await user.save(); + + res.status(200).send({ message: "Friend removed successfully" }); + } else { + res.status(404).send({ message: "User not found" }); + } + } catch (error) { + console.error("Error removing friend:", error); + res.status(500).send({ message: "Internal server error" }); } - } catch (error) { - console.error("Error removing friend:", error); - res.status(500).send({ message: "Internal server error" }); - } -}); + }, +); export { router as userEndpoints }; diff --git a/backend/tsconfig.json b/backend/tsconfig.json new file mode 100644 index 0000000..0557b5b --- /dev/null +++ b/backend/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "lib": [ + "es6" + ], + "target": "es6", + "module": "ESNext", + "moduleResolution": "node", + "outDir": "dist", + "resolveJsonModule": true, + "emitDecoratorMetadata": true, + "esModuleInterop": true, + "experimentalDecorators": true, + "sourceMap": true + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "**/*.spec.ts"] + } \ No newline at end of file diff --git a/frontend/lib/deletes.tsx b/frontend/lib/deletes.tsx index 0dfbfde..e51df0b 100644 --- a/frontend/lib/deletes.tsx +++ b/frontend/lib/deletes.tsx @@ -1,6 +1,7 @@ import { ObjectId } from "mongoose"; import { IBasket } from "../../backend/models/basketSchema"; import { IItem } from "../../backend/models/itemSchema"; +import { fetchGroup } from "./fetches"; const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; const token = localStorage.getItem("token"); @@ -9,6 +10,9 @@ export const handleDeleteGroup = async (groupId: string) => { try { const response = await fetch(`${vite_backend_url}/groups/${groupId}`, { method: "DELETE", + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, }); if (!response.ok) { throw new Error(`Error: ${response.statusText}`); @@ -19,10 +23,52 @@ export const handleDeleteGroup = async (groupId: string) => { } }; +export const handleDeleteAllBasketsAndItems = async (groupId: string) => { + try { + const data = await fetchGroup(groupId); + if (data) { + const group = await data.json(); + const groupBaskets = group.baskets; + for (const basketId of groupBaskets) { + // Delete all items in the basket + await handleDeleteAllItemsInBasket(basketId); + // Delete the basket itself + await handleDeleteBasket(basketId); + } + } + console.log("All baskets and their items deleted successfully"); + } catch (error) { + console.error( + "There was an error deleting baskets and items in the group", + error + ); + } +}; + +export const handleDeleteItem = async (itemId: string) => { + try { + const response = await fetch(`${vite_backend_url}/items/${itemId}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (!response.ok) { + throw new Error(`Error: ${response.statusText}`); + } + console.log("item deleted successfully"); + } catch (error) { + console.error("There was an error deleting the item", error); + } +}; + export const handleDeleteBasket = async (basketId: string) => { try { const response = await fetch(`${vite_backend_url}/baskets/${basketId}`, { method: "DELETE", + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, }); if (!response.ok) { throw new Error(`Error: ${response.statusText}`); @@ -32,6 +78,128 @@ export const handleDeleteBasket = async (basketId: string) => { console.error("There was an error deleting the basket", error); } }; +export const handleDeleteGroupFromUsers = async ( + groupId: string, + userIds: string[] +) => { + try { + // Iterate over each userId + for (const userId of userIds) { + const response = await fetch(`${vite_backend_url}/users/${userId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (response.ok) { + const user = await response.json(); + const userGroups = user.groups; + + // Remove the group from the user's groups + const updatedGroups = userGroups.filter((id: string) => id !== groupId); + + // Update the user object + user.groups = updatedGroups; + + // Send the updated user data back to the server + const updateResponse = await fetch( + `${vite_backend_url}/users/${userId}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ groups: updatedGroups }), + } + ); + + if (updateResponse.ok) { + console.log(`Group removed successfully from user ${userId}`); + } else { + console.log(`Failed to update the user ${userId}`); + } + } else { + console.log(`Failed to fetch the user data for user ${userId}`); + } + } + } catch (error) { + console.log("An error occurred:", error); + } +}; + +export const handleDeleteBasketFromGroup = async ( + groupId: string, + basketId: string +) => { + try { + const response = await fetch(`${vite_backend_url}/groups/${groupId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (response.ok) { + const group = await response.json(); + const groupBaskets = group.baskets; + + // Remove the basketId from the group's baskets + const updatedBaskets = groupBaskets.filter( + (id: string) => id !== basketId + ); + + // Update the group object + group.baskets = updatedBaskets; + + // Send the updated group data back to the server + const updateResponse = await fetch( + `${vite_backend_url}/groups/${groupId}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ baskets: updatedBaskets }), + } + ); + + if (updateResponse.ok) { + console.log("Basket removed successfully"); + } else { + console.log("Failed to update the group"); + } + } else { + console.log("Failed to fetch the group data"); + } + } catch (error) { + console.log("An error occurred:", error); + } +}; + +export const handleDeleteAllItemsInBasket = async (basketId: string) => { + try { + // Fetch all items in the basket + const response = await fetch(`${vite_backend_url}/baskets/${basketId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (!response.ok) { + throw new Error(`Error fetching items: ${response.statusText}`); + } + + const basket = await response.json(); + const items = basket.items; + + // Delete each item in the basket + for (const itemId of items) { + await handleDeleteItem(itemId); + } + + console.log("All items in the basket deleted successfully"); + } catch (error) { + console.error("There was an error deleting items in the basket", error); + } +}; export const removeFriendFromUserByFriendId = async ( friendId: string, @@ -44,6 +212,7 @@ export const removeFriendFromUserByFriendId = async ( method: "DELETE", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify({ friendId: friendId }), } @@ -57,42 +226,6 @@ export const removeFriendFromUserByFriendId = async ( } }; -export const deleteItem = async ( - item: IItem, - basket: string | undefined = undefined -) => { - try { - if (!item.basket && !basket) - throw Error("Item missing basket and basket unsupplied."); - fetch( - `${vite_backend_url}/baskets/${item.basket ? item.basket : basket}/removeitem`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ _id: item._id }), - } - ) - .then((res) => { - if (res.status === 200) { - return fetch(`${vite_backend_url}/items/${item._id}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${token}`, - }, - }); - } - }) - .catch((err) => { - throw err; - }); - } catch (error) { - console.log(`Error deleting ${item.name}: ${error}`); - } -}; - export const removeItemFromBasketAndDelete = async ( baskets: IBasket[], item: IItem @@ -127,3 +260,35 @@ export const removeItemFromBasketAndDelete = async ( ); } }; + +// In progress +export const deleteItemWithBasketString = (item: IItem, bid: string = "") => { + if (!item.basket && bid === "") throw "Missing basket id."; + fetch( + `${vite_backend_url}/baskets/${item?.basket ? item.basket : bid}/removeitem`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ item: item._id }), + } + ) + .then((res) => { + if (res.status === 200) { + fetch(`${vite_backend_url}/items/${item._id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }).then((response) => { + console.log("Response: ", response); + }); + } else Promise.reject("failed to remove item from basket"); + }) + .catch((error) => { + console.log(error); + }); +}; diff --git a/frontend/lib/edits.tsx b/frontend/lib/edits.tsx index f1bf9d7..105f4ca 100644 --- a/frontend/lib/edits.tsx +++ b/frontend/lib/edits.tsx @@ -1,8 +1,9 @@ import { ObjectId } from "mongoose"; -import { IBasket } from "../../backend/models/basketSchema"; -import { IItem } from "../../backend/models/itemSchema"; -import { IUser } from "../../backend/models/userSchema"; -import { IGroup } from "../../backend/models/groupSchema"; +import { IBasket } from "backend/models/basketSchema"; +import { IItem } from "backend/models/itemSchema"; +import { IUser } from "backend/models/userSchema"; +import { IGroup } from "backend/models/groupSchema"; +import { handleDeleteBasket } from "./deletes"; const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; @@ -44,6 +45,7 @@ export const editGroup = async (groupId: string, groupData: updatedGroup) => { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(groupData), }); @@ -51,12 +53,13 @@ export const editGroup = async (groupId: string, groupData: updatedGroup) => { export const editBasket = async ( basketId: string, - basketData: updatedBasket, + basketData: updatedBasket ) => { return fetch(`${vite_backend_url}/baskets/${basketId}`, { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(basketData), }); @@ -64,12 +67,13 @@ export const editBasket = async ( export const addItemToBasket = async ( basketId: ObjectId, - basketItems: ObjectId[], + basketItems: ObjectId[] ) => { return fetch(`${vite_backend_url}/baskets/${basketId}`, { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify({ items: basketItems }), }); @@ -80,6 +84,7 @@ export const editItem = async (itemId: string, itemData: updatedItem) => { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(itemData), }); @@ -88,7 +93,7 @@ export const editItem = async (itemId: string, itemData: updatedItem) => { export const moveItem = async ( userBaskets: IBasket[], newBasket: IBasket, - item: IItem, + item: IItem ) => { try { console.log(userBaskets); @@ -106,7 +111,7 @@ export const moveItem = async ( body: JSON.stringify({ items: newBasketsItems, }), - }, + } ); if (removeItemFromBasket.ok) { console.log("Item removed from basket successfully"); @@ -135,7 +140,7 @@ export const moveItem = async ( Authorization: `Bearer ${token}`, }, body: JSON.stringify({ items: [...newBasket.items, item._id] }), - }, + } ); if (updatedBasket.ok) { console.log("Item added to basket successfully"); @@ -149,12 +154,13 @@ export const moveItem = async ( export const editUser = async ( userId: string, - userData: { firstName: string; lastName: string }, + userData: { firstName: string; lastName: string } ) => { return fetch(`${vite_backend_url}/users/${userId}`, { method: "PATCH", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(userData), }); @@ -181,3 +187,65 @@ export const addUserToGroup = async (group: IGroup, users: ObjectId[]) => { body: JSON.stringify({ members: users }), }); }; + +export const addFriendToUser = async ( + user: IUser, + updatedFriends: ObjectId[] +) => { + return fetch(`${vite_backend_url}/users/${user._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ friends: updatedFriends }), + }); +}; + +// Remove a user from a group by first removing all of their baskets (that are ONLY associated with them) +export const removeUserFromGroup = async (group: IGroup, user: IUser) => { + fetch(`${vite_backend_url}/groups/removeuser/${group._id}&${user._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: "", + }) + .then((res) => { + if (res.status === 207) { + return res.json(); + } else if (res.status === 200) { + console.log("Successfully removed user."); + return; + } else Promise.reject(`Request failed: ${res.status} ${res.statusText}`); + }) + .then((json) => { + if (!json) return; + for (let i = 0; i < json.length; i++) { + handleDeleteBasket(json[i]); + removeBasketFromGroup(group, json[i]); + } + // Remove user from group + }) + .then(() => { + // Remove group from user + }) + .catch((error) => { + console.log("Error deleting user: ", error); + }); +}; + +export const removeBasketFromGroup = async (group: IGroup, bid: string) => { + fetch(`${vite_backend_url}/groups/removebasket/${group._id}&${bid}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: "", + }).then((res) => { + if (res.status === 200) { + } else Promise.reject("Failed to remove the basket from the group."); + }); +}; diff --git a/frontend/lib/fetches.tsx b/frontend/lib/fetches.tsx index 15f8b8a..b1945a6 100644 --- a/frontend/lib/fetches.tsx +++ b/frontend/lib/fetches.tsx @@ -2,20 +2,33 @@ import { IUser } from "../../backend/models/userSchema"; import { IGroup } from "../../backend/models/groupSchema"; import { IBasket } from "../../backend/models/basketSchema"; import { ObjectId } from "mongoose"; +import { addUserToGroup, addGroupToUser } from "./edits"; const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; export const fetchBasket = async (basketId: string) => { - return fetch(`${vite_backend_url}/baskets/${basketId}`); + return fetch(`${vite_backend_url}/baskets/${basketId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); }; export const fetchItem = async (itemId: string) => { - return fetch(`${vite_backend_url}/items/${itemId}`); + return fetch(`${vite_backend_url}/items/${itemId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); }; export const fetchGroupById = async (groupId: string) => { try { - const res = await fetch(`${vite_backend_url}/groups/${groupId}`); + const res = await fetch(`${vite_backend_url}/groups/${groupId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.ok) { return res.json(); } else { @@ -26,17 +39,37 @@ export const fetchGroupById = async (groupId: string) => { } }; +export const fetchGroup = async (groupId: string) => { + return fetch(`${vite_backend_url}/groups/${groupId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); +}; + export const fetchUser = async (userId: ObjectId) => { - return fetch(`${vite_backend_url}/users/${userId}`); + return fetch(`${vite_backend_url}/users/${userId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); }; export const fetchUserWithString = async (userId: string) => { - return fetch(`${vite_backend_url}/users/${userId}`); + return fetch(`${vite_backend_url}/users/${userId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); }; export const fetchUserGroupsByUser = async (user: IUser) => { const groupPromises = user.groups.map(async (group: ObjectId) => { - const res = await fetch(`${vite_backend_url}/groups/${group}`); + const res = await fetch(`${vite_backend_url}/groups/${group}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const data = await res.json(); return data; @@ -49,7 +82,11 @@ export const fetchUserGroupsByUser = async (user: IUser) => { export const fetchUserFriendsByUser = async (user: IUser) => { const friendPromises = user.friends.map(async (friend: ObjectId) => { - const res = await fetch(`${vite_backend_url}/users/${friend}`); + const res = await fetch(`${vite_backend_url}/users/${friend}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const data = await res.json(); return data; @@ -60,29 +97,32 @@ export const fetchUserFriendsByUser = async (user: IUser) => { return tempFriendList; }; -export const addFriendToGroup = async (friendId: string, groupId: string) => { +export const addFriendToGroup = async (friendId: ObjectId, groupId: string) => { try { - const res = await fetch(`${vite_backend_url}/users/${friendId}`); - let friend; + const group = await fetchGroupById(groupId); + const res = await fetchUser(friendId); - if (res.ok) { + let friend; + if (res.ok && group) { friend = await res.json(); + if (!group.members.includes(friendId)) { + group.members.push(friendId); + console.log("Pushed friend ID to group's member list"); + const updatedRes1 = await addUserToGroup(group, group.members); + if (updatedRes1.ok) { + console.log("Friend added to group's member list successfully"); + } else { + console.error("Failed to update group"); + } + } if (!friend.groups.includes(groupId)) { friend.groups.push(groupId); console.log("Pushed to list"); - const updatedRes = await fetch( - `${vite_backend_url}/users/${friendId}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ groups: friend.groups }), - }, - ); + const updatedRes = await addGroupToUser(friend, friend.groups); if (updatedRes.ok) { console.log("Friend added to group successfully"); + window.location.reload(); } else { console.error("Failed to update user"); } @@ -90,7 +130,7 @@ export const addFriendToGroup = async (friendId: string, groupId: string) => { console.log("Friend is already in group"); } } else { - console.log("Group not Found"); + console.log("User not Found", res.status); } } catch (error) { console.error("Error adding friend:", error); @@ -99,7 +139,11 @@ export const addFriendToGroup = async (friendId: string, groupId: string) => { export const fetchGroupBaskets = async (group: IGroup) => { const basketPromises = group.baskets.map(async (basket) => { - const res = await fetch(`${vite_backend_url}/baskets/${basket}`); + const res = await fetch(`${vite_backend_url}/baskets/${basket}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const data = await res.json(); return data; @@ -117,7 +161,11 @@ export const fetchBasketItems = async (basket: IBasket) => { return []; } const itemPromises = basket.items.map(async (item) => { - const res = await fetch(`${vite_backend_url}/items/${item}`); + const res = await fetch(`${vite_backend_url}/items/${item}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const data = await res.json(); return data; @@ -129,7 +177,11 @@ export const fetchBasketItems = async (basket: IBasket) => { }; export const fetchUserBaskets = async (userId: string) => { - const res = await fetch(`${vite_backend_url}/baskets`); + const res = await fetch(`${vite_backend_url}/baskets`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const allBaskets = await res.json(); const userBaskets = [] as IBasket[]; @@ -144,7 +196,11 @@ export const fetchUserBaskets = async (userId: string) => { export const fetchGroups = async (userGroups: ObjectId[]) => { const groupPromises = userGroups.map(async (group) => { - const res = await fetch(`${vite_backend_url}/groups/${group}`); + const res = await fetch(`${vite_backend_url}/groups/${group}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.status === 200) { const data = await res.json(); return data; @@ -159,13 +215,17 @@ export const fetchMembers = async (memberIds: ObjectId[]) => { try { const fetchedMembers = await Promise.all( memberIds.map(async (memberId) => { - const res = await fetch(`${vite_backend_url}/users/${memberId}`); + const res = await fetch(`${vite_backend_url}/users/${memberId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); if (res.ok) { return res.json(); } else { throw new Error(`Failed to fetch user: ${res.statusText}`); } - }), + }) ); return fetchedMembers as IUser[]; } catch (err) { @@ -178,10 +238,12 @@ export const loginUser = async (credentials: { username: string; password: string; }) => { + console.log(vite_backend_url); const res = await fetch(`${vite_backend_url}/login`, { method: "POST", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(credentials), }); diff --git a/frontend/lib/posts.tsx b/frontend/lib/posts.tsx index 2d9c096..cf91940 100644 --- a/frontend/lib/posts.tsx +++ b/frontend/lib/posts.tsx @@ -38,6 +38,7 @@ export const createUser = async (user: newUser) => { method: "POST", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(user), }); @@ -59,6 +60,7 @@ export const loginUser = async (credentials: credentials) => { method: "POST", headers: { "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, }, body: JSON.stringify(credentials), }); diff --git a/frontend/public/HomePage2.jpg b/frontend/public/HomePage.jpg similarity index 100% rename from frontend/public/HomePage2.jpg rename to frontend/public/HomePage.jpg diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 66636ea..15de74a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -91,15 +91,6 @@ function App() { path="/login" element={} /> - - } - /> } + path="/profile" + element={ + + } /> - {/* added route for individual group page */} } @@ -127,6 +122,12 @@ function App() { /> } /> + + } + /> diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index 05ecc3a..3789a8e 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -22,9 +22,15 @@ interface Props { basketId: string; groupMembers: IUser[]; LoggedInUser: IUser | null; + groupId: string; } -const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { +const BasketComp = ({ + basketId, + groupMembers, + LoggedInUser, + groupId, +}: Props) => { const [basketObj, setBasket] = useState({} as IBasket); const [error, setError] = useState({ msg: "", @@ -172,7 +178,10 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { <> )} {isMemberOfBasket ? ( - + ) : ( <> )} @@ -205,6 +214,7 @@ const BasketComp = ({ basketId, groupMembers, LoggedInUser }: Props) => { return ( diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index a21fd1c..9820e6f 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -13,14 +13,15 @@ import { fetchItem } from "../../lib/fetches"; import { IItem } from "../../../backend/models/itemSchema"; import EditItem from "./EditItem"; import { DeleteIcon } from "@chakra-ui/icons"; -import { deleteItem } from "../../lib/deletes"; +import { deleteItemWithBasketString } from "../../lib/deletes"; interface Props { itemId: string; + bid: string; basketMemberView: boolean; } -const BasketItem = ({ itemId, basketMemberView }: Props) => { +const BasketItem = ({ itemId, bid, basketMemberView }: Props) => { const [item, setItem] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState({ @@ -52,8 +53,8 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { const removeItem = async (item: IItem) => { console.log(item); - deleteItem(item).catch((err) => console.log(err)); - window.location.reload(); + deleteItemWithBasketString(item, bid); + //window.location.reload(); }; if (!basketMemberView && item?.isPrivate) { diff --git a/frontend/src/components/EditBasket.tsx b/frontend/src/components/EditBasket.tsx index 18fce2c..e067264 100644 --- a/frontend/src/components/EditBasket.tsx +++ b/frontend/src/components/EditBasket.tsx @@ -17,7 +17,11 @@ import { } from "@chakra-ui/react"; import React, { useState, useEffect } from "react"; import { fetchBasket } from "../../lib/fetches"; -import { handleDeleteBasket } from "../../lib/deletes"; +import { + handleDeleteAllItemsInBasket, + handleDeleteBasket, + handleDeleteBasketFromGroup, +} from "../../lib/deletes"; import { editBasket } from "../../lib/edits"; //Add Radio for boolean @@ -25,9 +29,10 @@ import { editBasket } from "../../lib/edits"; interface Props { basketId: string; + groupId: string; } -const EditBasket: React.FC = ({ basketId }) => { +const EditBasket: React.FC = ({ basketId, groupId }) => { // Note: Colors not added yet, just basic structure const [isEditing, setIsEditing] = useState(false); const [editedName, setEditedName] = useState(""); @@ -62,6 +67,25 @@ const EditBasket: React.FC = ({ basketId }) => { fetchBasketData(); }, [basketId]); + const handleDelete = async (groupId: string, basketId: string) => { + console.log("got here"); + console.log(groupId); + console.log(basketId); + try { + // Wait for each asynchronous deletion to complete + await handleDeleteBasketFromGroup(groupId, basketId); + await handleDeleteAllItemsInBasket(basketId); + await handleDeleteBasket(basketId); + + console.log("All deletions completed successfully"); + + // Reload the page after all deletions are complete + window.location.reload(); + } catch (error) { + console.error("An error occurred while deleting:", error); + } + }; + const handleSaveChanges = async () => { try { const updatedBasket = { @@ -153,7 +177,7 @@ const EditBasket: React.FC = ({ basketId }) => { _hover={{ bg: "#ff8366", color: "var(--col-dark)" }} mt={2} ml="auto" - onClick={() => handleDeleteBasket(basketId)} + onClick={() => handleDelete(groupId, basketId)} > Delete diff --git a/frontend/src/components/EditGroup.tsx b/frontend/src/components/EditGroup.tsx index 8d246ca..3bd8551 100644 --- a/frontend/src/components/EditGroup.tsx +++ b/frontend/src/components/EditGroup.tsx @@ -20,18 +20,36 @@ import { } from "@chakra-ui/react"; import React, { useState, useEffect } from "react"; import {} from "@chakra-ui/react"; -import { fetchGroupById } from "../../lib/fetches"; -import { handleDeleteGroup } from "../../lib/deletes"; +import { fetchGroupById, fetchUser } from "../../lib/fetches"; +import { + handleDeleteAllBasketsAndItems, + handleDeleteGroup, + handleDeleteGroupFromUsers, +} from "../../lib/deletes"; import { editGroup } from "../../lib/edits"; +import { useNavigate } from "react-router-dom"; //Add Radio for boolean //Number input for number type interface Props { GroupId: string; + members: string[] | []; + LoggedInUser: any; + setUser: any; } -const Editgroup: React.FC = ({ GroupId }) => { +const Editgroup: React.FC = ({ + GroupId, + members, + LoggedInUser, + setUser, +}: { + GroupId: string; + members: string[] | []; + LoggedInUser: any; + setUser: any; +}) => { // Note: Colors not added yet, just basic structure const [isEditing, setIsEditing] = useState(false); const [editedName, setEditedName] = useState(""); @@ -43,6 +61,7 @@ const Editgroup: React.FC = ({ GroupId }) => { groupDesc: "", groupPub: "", }); + const navigate = useNavigate(); useEffect(() => { const fetchgroupData = async () => { @@ -66,6 +85,25 @@ const Editgroup: React.FC = ({ GroupId }) => { fetchgroupData(); }, [GroupId]); + const handleDelete = async (groupId: string, userIds: string[]) => { + console.log("here"); + console.log(userIds); + try { + await handleDeleteGroupFromUsers(groupId, userIds); + await handleDeleteAllBasketsAndItems(groupId); + await handleDeleteGroup(groupId); + const res = await fetchUser(LoggedInUser._id); + if (res.ok) { + const updatedUser = await res.json(); + console.log("here: ", updatedUser); + setUser(updatedUser); + } + navigate("/groups"); + } catch (error) { + console.error("An error occurred while deleting:", error); + } + }; + const handleSaveChanges = async () => { try { const updatedgroup = { @@ -206,7 +244,9 @@ const Editgroup: React.FC = ({ GroupId }) => { _hover={{ bg: "#ff8366", color: "var(--col-dark)" }} mt={2} ml="auto" - onClick={() => handleDeleteGroup(GroupId)} + onClick={() => + GroupId && members && handleDelete(GroupId, members) + } > Delete diff --git a/frontend/src/components/Friends_List_Component.tsx b/frontend/src/components/Friends_List_Component.tsx index 6c74042..cc143fe 100644 --- a/frontend/src/components/Friends_List_Component.tsx +++ b/frontend/src/components/Friends_List_Component.tsx @@ -25,9 +25,12 @@ import { fetchUser, fetchUserGroupsByUser, fetchUserFriendsByUser, + fetchUserWithString, } from "../../lib/fetches"; import { removeFriendFromUserByFriendId } from "../../lib/deletes"; import { addFriendToGroup } from "../../lib/fetches"; +import { ObjectId } from "mongoose"; +import { addFriendToUser } from "../../lib/edits"; type Props = { initialUserId?: string; @@ -88,8 +91,8 @@ const Friends_List: React.FC = ({ if (userId == LoggedInUser) { console.log("Cannot add yourself as friend"); } else { - const res = await fetch(`http://localhost:3001/users/${userId}`); - const res2 = await fetch(`http://localhost:3001/users/${LoggedInUser}`); + const res = await fetchUserWithString(userId); + const res2 = await fetchUser(LoggedInUser); let user; let friend; if (res.ok && res2.ok) { @@ -100,16 +103,7 @@ const Friends_List: React.FC = ({ user.friends.push(friend._id); console.log("Pushed to list"); - const updatedRes = await fetch( - `http://localhost:3001/users/${LoggedInUser}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ friends: user.friends }), - }, - ); + const updatedRes = await addFriendToUser(user, user.friends); console.log("Past Patch"); if (updatedRes.ok) { setFriends([...friends, friend]); @@ -145,7 +139,7 @@ const Friends_List: React.FC = ({ } }; - const handleGroupClick = async (groupId: string, friendId: string) => { + const handleGroupClick = async (groupId: string, friendId: ObjectId) => { try { console.log(`Group ID: ${groupId} clicked`); console.log(`USER ID: ${friendId} clicked`); @@ -208,7 +202,7 @@ const Friends_List: React.FC = ({ onClick={() => handleGroupClick( group._id.toString(), - friend._id.toString(), + friend._id, ) } > diff --git a/frontend/src/components/NewGroupOptions.tsx b/frontend/src/components/NewGroupOptions.tsx index ef96267..fc4c973 100644 --- a/frontend/src/components/NewGroupOptions.tsx +++ b/frontend/src/components/NewGroupOptions.tsx @@ -32,7 +32,7 @@ const NewGroupOptions = ({ const createGroup = async ( groupName: string, privateGroup: boolean, - description: string, + description: string ) => { const groupData = { groupName, @@ -58,7 +58,7 @@ const NewGroupOptions = ({ return ( { postGroup( group.name, group.isPublic === "on", - group.description === "" ? "No description given" : group.description, + group.description === "" ? "No description given" : group.description ); setGroup({ name: "", isPublic: "off", description: "" }); }; diff --git a/frontend/src/components/RemoveFromGroup.tsx b/frontend/src/components/RemoveFromGroup.tsx new file mode 100644 index 0000000..ae8c6ab --- /dev/null +++ b/frontend/src/components/RemoveFromGroup.tsx @@ -0,0 +1,82 @@ +import { + Box, + Button, + Flex, + Popover, + PopoverArrow, + PopoverBody, + PopoverCloseButton, + PopoverContent, + PopoverHeader, + PopoverTrigger, + VStack, +} from "@chakra-ui/react"; +import { IGroup } from "backend/models/groupSchema"; +import { IUser } from "backend/models/userSchema"; +import { useEffect, useState } from "react"; + +interface Props { + groupMembers: IUser[]; + LoggedInUser: IUser; + group: IGroup; +} + +const RemoveFromGroup = ({ groupMembers, LoggedInUser, group }: Props) => { + // Initialize the members state with the filtered memberid prop + const [displayMembers, setMembers] = useState([]); + + const onRemoveMember = (member: IUser) => { + //window.location.reload(); + }; + + useEffect(() => { + setMembers( + groupMembers?.filter((member) => member._id !== LoggedInUser._id) + ); + }, [groupMembers, LoggedInUser]); + + return ( +
+ + + + + + + + Group members + + {displayMembers?.map((member) => ( + + {member.username} + + + ))} + + + +
+ ); +}; + +export default RemoveFromGroup; diff --git a/frontend/src/pages/IndividualGroupPage.tsx b/frontend/src/pages/IndividualGroupPage.tsx index c409d8c..029aacb 100644 --- a/frontend/src/pages/IndividualGroupPage.tsx +++ b/frontend/src/pages/IndividualGroupPage.tsx @@ -29,15 +29,21 @@ import Editgroup from "../components/EditGroup"; import NewBasketOptions from "../components/NewBasketOptions"; import SendInviteToGroup from "../components/SendInvite"; import { fetchUserWithString } from "../../lib/fetches"; -import RemoveFromGroup from "../components/RemoveFromGroup"; const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; type Props = { LoggedInUser: IUser | null; + setUser: any; }; -const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { +const IndividualGroupPage: React.FC = ({ + LoggedInUser, + setUser, +}: { + LoggedInUser: any; + setUser: any; +}) => { const { groupId } = useParams<{ groupId: string }>(); const [group, setGroup] = useState(null); const [groupBaskets, setGroupBaskets] = useState(null); @@ -45,6 +51,10 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { const [members, setMembers] = useState([]); const [friends, setFriends] = useState([]); const navigate = useNavigate(); + const memberIds = members.map((member) => member._id.toString()); + console.log(LoggedInUser); + console.log(friends); + console.log("These are the members", members); const fetchFriends = async (friendIds: string[]) => { try { @@ -58,6 +68,7 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { } }) ); + setFriends(fetchedFriends); } catch (err) { console.error(err); @@ -99,17 +110,20 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { }; useEffect(() => { - //console.log(`Loading: ${loading}`); + console.log(`Loading: ${loading}`); + if (!LoggedInUser) { + navigate("/login"); + } if (groupId) { fetchGroup(String(groupId)) .then((group) => { - //console.log(`Fetched group: ${group}`); + console.log(`Fetched group: ${group}`); fetchGroupMembers(group as IGroup) .then(() => { - //console.log(`Fetched group members: ${members}`); + console.log(`Fetched group members: ${members}`); fetchBaskets(group as IGroup) .then(() => { - //console.log(`Fetched group baskets: ${groupBaskets}`); + console.log(`Fetched group baskets: ${groupBaskets}`); setLoading(false); }) .catch((err) => { @@ -126,8 +140,6 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { } }, [loading]); - const isGroupOwner = members && members[0]?._id === LoggedInUser?._id; - return ( = ({ LoggedInUser }) => { flexDirection="column" padding="20px" flex="1" - overflowY="auto" + overflowY="scroll" alignItems="center" > {loading ? ( @@ -205,11 +217,13 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { {group.groupName} - {groupId && isGroupOwner ? ( - <> - - - + {groupId ? ( + ) : ( <> )} @@ -280,25 +294,30 @@ const IndividualGroupPage: React.FC = ({ LoggedInUser }) => { - - Baskets - - + Baskets + - + {groupBaskets && members ? ( - groupBaskets.map((basket) => ( - - )) + groupBaskets.map( + (basket) => ( + console.log(group), + console.log(basket), + ( + + ) + ) + ) ) : ( No baskets available )} diff --git a/frontend/src/pages/ItemsPage.tsx b/frontend/src/pages/ItemsPage.tsx index cded6a9..661b395 100644 --- a/frontend/src/pages/ItemsPage.tsx +++ b/frontend/src/pages/ItemsPage.tsx @@ -37,6 +37,8 @@ const ItemsPage: React.FC = ({ .catch((err) => { console.log(`Terrible error occurred! ${err}`); }); + } else { + navigate("/login"); } }, [stateVariable.user]); diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index a7b63f0..94f8b88 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -24,6 +24,8 @@ const LoginPage = ({ updateState }: { updateState: any }) => { const handleSubmit = async () => { console.log("submitting form"); + console.log(username); + console.log(password); if (username === "" || password === "") { alert("Please fill out all fields"); return; diff --git a/frontend/src/pages/MyGroupsPage.tsx b/frontend/src/pages/MyGroupsPage.tsx index ebb4fbb..6996332 100644 --- a/frontend/src/pages/MyGroupsPage.tsx +++ b/frontend/src/pages/MyGroupsPage.tsx @@ -13,10 +13,9 @@ import SkeletonGroup from "../components/SkeletonGroup"; import { IoIosSwap } from "react-icons/io"; import SearchBar from "../components/SearchBar"; import PageSelector from "../components/PageSelector"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import "../styles/MyGroups.css"; import NewGroupOptions from "../components/NewGroupOptions"; - import { IGroup } from "../../../backend/models/groupSchema"; import { IUser } from "../../../backend/models/userSchema"; import { fetchGroups } from "../../lib/fetches"; @@ -39,12 +38,13 @@ const GroupPage: React.FC = ({ const [groupList, setGroupList] = useState([]); const [filteredGroups, setFilteredGroups] = useState([]); const [selectedPage, setSelectedPage] = useState(1); - const [loading, setLoading] = useState(true); const gridDims = [2, 4]; const skelIds: number[] = []; + const navigate = useNavigate(); for (let i = 0; i < gridDims[0] * gridDims[1]; i++) { skelIds.push(i); } + console.log(stateVariable.user); const searchGroups = (input: string) => { if (input === "") { @@ -65,11 +65,12 @@ const GroupPage: React.FC = ({ .then((tempGroupList) => { setGroupList(tempGroupList); setFilteredGroups(groupList); // Initialize with full list - setLoading(false); }) .catch((err) => { console.log(`Terrible error occurred! ${err}`); }); + } else { + navigate("/login"); } }, [stateVariable.user]); @@ -159,7 +160,7 @@ const GroupPage: React.FC = ({ justifyContent="center" alignItems="center" > - {loading ? ( + {stateVariable.user?.groups.length !== 0 && filteredGroups.length === 0 ? ( skelIds.map((id) => { return ( @@ -196,8 +197,7 @@ const GroupPage: React.FC = ({ ) : ( - No groups found! Do you want to add one? (add button not yet - implemented) + No groups found! Do you want to add one? )} diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index 4b8b88b..6b9a8c7 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -1,7 +1,8 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Box, Grid, GridItem, Heading } from "@chakra-ui/react"; import UserProfile from "../components/UserProfile"; import Friends_List from "../components/Friends_List_Component"; +import { useNavigate } from "react-router-dom"; interface ProfilePageProps { LoggedInUser: string; @@ -12,6 +13,14 @@ const ProfilePage: React.FC = ({ LoggedInUser, avatarColor, }) => { + const navigate = useNavigate(); + useEffect(() => { + if (!LoggedInUser) { + navigate("/login"); + } + } + ); + return ( Date: Sun, 2 Jun 2024 19:42:15 -0700 Subject: [PATCH 22/27] RemoveUser --- backend/routes/basketRoutes.ts | 48 +++++++++++---------- frontend/src/components/RemoveFromGroup.tsx | 19 ++++---- frontend/src/pages/IndividualGroupPage.tsx | 26 ++++++----- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/backend/routes/basketRoutes.ts b/backend/routes/basketRoutes.ts index ef75b6f..254c24d 100644 --- a/backend/routes/basketRoutes.ts +++ b/backend/routes/basketRoutes.ts @@ -106,30 +106,34 @@ router.patch("/:id", authenticateUser, async (req: Request, res: Response) => { } }); -router.patch("/:bid/removeitem", async (req: Request, res: Response) => { - console.log("running delete"); - const { bid } = req.params; - const itemToRemove = req.body; - try { - const b: IBasket | null = await Basket.findById(bid); - if (!b) res.status(404).send("Basket not found."); - const newItemList = b?.items.filter( - (id) => id.toString() != itemToRemove._id - ); - if (newItemList?.length === b?.items.length) - res.status(404).send("Item not found"); - const partial: Partial = { items: newItemList }; - const updatedBasket = await Basket.findByIdAndUpdate(bid, partial, { - new: true, - runValidators: true, - }).lean(); +router.patch( + "/:bid/removeitem", + authenticateUser, + async (req: Request, res: Response) => { + const { bid } = req.params; + const { item } = req.body; + try { + const b: IBasket | null = await Basket.findById(bid); + if (!b) return res.status(404).send("Basket not found."); - res.status(200).send(updatedBasket); - } catch (error) { - console.error("Error deleting the Basket:", error); - res.status(500).send("Internal Server Error"); + const newItemList = b.items.filter((id) => id.toString() !== item); + if (newItemList?.length === b?.items.length) { + return res.status(404).send("Item not found"); + } + + const partial: Partial = { items: newItemList }; + const updatedBasket = await Basket.findByIdAndUpdate(bid, partial, { + new: true, + runValidators: true, + }).lean(); + + res.status(200).send(updatedBasket); + } catch (error) { + console.error("Error removing an item from the Basket:", error); + res.status(500).send("Internal Server Error"); + } } -}); +); router.delete("/:id", authenticateUser, async (req: Request, res: Response) => { connectDB(); diff --git a/frontend/src/components/RemoveFromGroup.tsx b/frontend/src/components/RemoveFromGroup.tsx index ae8c6ab..5fae09c 100644 --- a/frontend/src/components/RemoveFromGroup.tsx +++ b/frontend/src/components/RemoveFromGroup.tsx @@ -13,27 +13,26 @@ import { } from "@chakra-ui/react"; import { IGroup } from "backend/models/groupSchema"; import { IUser } from "backend/models/userSchema"; +import { ObjectId } from "mongoose"; import { useEffect, useState } from "react"; interface Props { - groupMembers: IUser[]; LoggedInUser: IUser; group: IGroup; } -const RemoveFromGroup = ({ groupMembers, LoggedInUser, group }: Props) => { +const RemoveFromGroup = ({ LoggedInUser, group }: Props) => { // Initialize the members state with the filtered memberid prop - const [displayMembers, setMembers] = useState([]); + const [displayMembers, setMembers] = useState([]); - const onRemoveMember = (member: IUser) => { + const onRemoveMember = (member: ObjectId) => { + console.log("Removing member", member); //window.location.reload(); }; useEffect(() => { - setMembers( - groupMembers?.filter((member) => member._id !== LoggedInUser._id) - ); - }, [groupMembers, LoggedInUser]); + setMembers(group.members.filter((member) => member !== LoggedInUser._id)); + }, [group, LoggedInUser]); return (
@@ -58,12 +57,12 @@ const RemoveFromGroup = ({ groupMembers, LoggedInUser, group }: Props) => { {displayMembers?.map((member) => ( - {member.username} + {member.toString() /* FIX!!! */}