diff --git a/.github/workflows/main_gather-app-inv.yml b/.github/workflows/main_gather-app-307.yml similarity index 81% rename from .github/workflows/main_gather-app-inv.yml rename to .github/workflows/main_gather-app-307.yml index 33bd182..7140a63 100644 --- a/.github/workflows/main_gather-app-inv.yml +++ b/.github/workflows/main_gather-app-307.yml @@ -1,7 +1,7 @@ # 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 +name: Build and deploy Node.js app to Azure Web App - gather-app-307 on: push: @@ -24,8 +24,8 @@ jobs: - 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 + npm run build -w backend build --if-present + npm run test -w backend test --if-present working-directory: backend - name: Zip artifact for deployment @@ -59,14 +59,14 @@ jobs: - 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 }} + client-id: ${{ secrets.AZUREAPPSERVICE_CLIENTID_03BA6874737C4AC5A41D6370B2F87080 }} + tenant-id: ${{ secrets.AZUREAPPSERVICE_TENANTID_90BB6E72E2B34689BB0C942CB41E9672 }} + subscription-id: ${{ secrets.AZUREAPPSERVICE_SUBSCRIPTIONID_6D44115D9FF541AFB144E3E14FE8B81A }} - name: "Deploy to Azure Web App" id: deploy-to-webapp uses: azure/webapps-deploy@v2 with: - app-name: "gather-app-inv" + app-name: "gather-app-307" slot-name: "Production" package: . diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml index 44e0c26..3b517ac 100644 --- a/.github/workflows/remove-old-artifacts.yml +++ b/.github/workflows/remove-old-artifacts.yml @@ -3,7 +3,7 @@ name: Remove old artifacts on: schedule: # Every day at 1am - - cron: '0 1 * * *' + - cron: "0 1 * * *" jobs: remove-old-artifacts: @@ -11,10 +11,18 @@ jobs: timeout-minutes: 10 steps: - - name: Remove old artifacts - uses: c-hive/gha-remove-artifacts@v1.4.0 - with: - age: '5 days' # ' ', e.g. 5 days, 2 years, 90 seconds, parsed by Moment.js - # Optional inputs - # skip-tags: true - # skip-recent: 5 \ No newline at end of file + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up GitHub CLI + run: | + echo ${GITHUB_PAT} | gh auth login --with-token + env: + GITHUB_PAT: ${{ secrets.PAT_TOKEN }} + + - name: Remove old artifacts + uses: c-hive/gha-remove-artifacts@v1.4.0 + with: + age: "5 days" # ' ', e.g. 5 days, 2 years, 90 seconds, parsed by Moment.js + env: + GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }} 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 c8e8f49..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(); @@ -20,7 +20,12 @@ app.use((req: Request, res: Response, next: NextFunction) => { "Access-Control-Allow-Methods", "GET, POST, PATCH, OPTIONS, DELETE, PUT", ); - next(); + // Handle preflight requests + if (req.method === "OPTIONS") { + res.status(204).end(); // Respond with 204 No Content + } else { + next(); // Pass to the next middleware or route handler + } }); // Testing middleware diff --git a/backend/itemSchema.ts b/backend/itemSchema.ts deleted file mode 100644 index 5df94a1..0000000 --- a/backend/itemSchema.ts +++ /dev/null @@ -1,47 +0,0 @@ -import mongoose, { Schema } from "mongoose"; - -export type IItem = { - _id: Schema.Types.ObjectId; - eventName: string; - eventImage: string | null; - eventType: string; - location: string; - description: string; - wheelchairAccessible: boolean; - spanishSpeakingAccommodation: boolean; - startTime: Date; - endTime: Date; - volunteerEvent: boolean; - groupsAllowed: Schema.Types.ObjectId[] | null; - attendeeIds: Schema.Types.ObjectId[]; - registeredIds: Schema.Types.ObjectId[]; -}; - -// Mongoose schema -const itemSchema = new Schema({ - eventName: { type: String, required: true }, - eventImage: { type: String, required: false }, - eventType: { type: String, required: true }, - location: { type: String, required: true }, - description: { type: String, required: true }, - wheelchairAccessible: { type: Boolean, required: true }, - spanishSpeakingAccommodation: { type: Boolean, required: true }, - startTime: { type: Date, required: true }, - endTime: { type: Date, required: true }, - volunteerEvent: { type: Boolean, required: true }, - groupsAllowed: { type: [Schema.Types.ObjectId], required: false }, - attendeeIds: { - type: [Schema.Types.ObjectId], - required: false, - default: [], - }, - registeredIds: { - type: [Schema.Types.ObjectId], - required: true, - default: [], - }, -}); - -const Event = mongoose.models["items"] || mongoose.model("items", itemSchema); - -export default Event; 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 f2dd74c..e1bcf9c 100644 --- a/backend/routes/basketRoutes.ts +++ b/backend/routes/basketRoutes.ts @@ -1,76 +1,62 @@ import express from "express"; import { Request, Response } from "express"; -import Basket, { IBasket } from "../models/basketSchema"; -import connectDB from "../connection"; +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 users = await Basket.find({}); - if (users.length === 0) { - res.status(404).send("No baskets found"); // Return a 404 status code if no users are found + const baskets = await Basket.find({}); + if (baskets.length === 0) { + res.status(404).send("No baskets found"); // Return a 404 status code if no baskets are found } else { - res.send(users); // Return the found users + res.send(baskets); // Return the found baskets } } catch (error) { res.status(500).send("Internal Server Error"); // Handle any unexpected errors } }); -router.get("/:bid", async (req: Request, res: Response) => { - connectDB(); - // Use findById correctly with the id parameter from the request - const basket = await Basket.findById(req.params.bid).maxTimeMS(2000); - - // Check if group is null or undefined - if (!basket) { - return res.status(404).send("No basket found."); // Use return to exit the function after sending the response - } - - // Send the found user - res.send(basket); - console.log("Sent Group"); -}); - -router.get("/:basketid", 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) { - return res.status(404).send("No basket found"); // Use return to exit the function after sending the response - } +router.get( + "/:basketid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - // Send the found user - res.send(basketById); - console.log("Sent Basket:", basketById); - } catch (error) { - console.log("Now trying to find by BasketName"); try { - const basketsByName = await Basket.find({ - basketName: req.params.basketid, - }); - console.log(basketsByName); - if (!basketsByName) { - return res.status(404).send("No baskets found"); // Use return to exit the function after sending the response + // 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 user - res.send(basketsByName); - console.log("Sent Baskets", 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"); } - } -}); + }, +); -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); @@ -97,14 +83,14 @@ router.post("/", async (req: Request, res: Response) => { } }); -router.patch("/:id", async (req: Request, res: Response) => { - // Get user ID from URL +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 + const updatedData: Partial = req.body; // Not a full update, only partial try { connectDB(); - + console.log(updatedData); const updatedBasket = await Basket.findByIdAndUpdate(id, updatedData, { new: true, runValidators: true, @@ -120,14 +106,14 @@ 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 { const basket = await Basket.findByIdAndDelete(id); if (!basket) { - return res.status(404).send({ message: "basket not found" }); + return res.status(404).send({ message: "Basket not found" }); } res.status(200).send({ message: "Basket Deleted Successfully", basket }); diff --git a/backend/routes/groupRoutes.ts b/backend/routes/groupRoutes.ts index f148aae..67ea2f4 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 a9c71bf..d1fddbe 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,43 +20,47 @@ router.get("/", async (req: Request, res: Response) => { } }); -router.get("/:itemid", async (req: Request, res: Response) => { - // Ensure the database connection - connectDB(); +router.get( + "/:itemid", + authenticateUser, + async (req: Request, res: Response) => { + // Ensure the database connection + connectDB(); - try { - console.log("Here"); + try { + console.log("Here"); - // Use findById correctly with the id parameter from the request - const itemById = await Item.findById(req.params.itemid); + // 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 - if (!itemById) { - return res.status(404).send("No item found"); // Use return to exit the function after sending the response - } - // Send the found user - 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 + // Check if group 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 - 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 user + 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); @@ -93,7 +98,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 @@ -116,7 +121,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/cypress/e2e/groups.cy.ts b/frontend/cypress/e2e/groups.cy.ts index 4761cb5..dabc343 100644 --- a/frontend/cypress/e2e/groups.cy.ts +++ b/frontend/cypress/e2e/groups.cy.ts @@ -1,6 +1,7 @@ describe("retrieve remote groups", () => { it("retrieves", () => { - cy.request("http://localhost:3001/groups").then((res) => { + const vite_backend_url = Cypress.env("VITE_BACKEND_URL"); + cy.request(`${vite_backend_url}/groups`).then((res) => { console.log(res); }); }); diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 698b01a..95857ae 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -34,4 +34,4 @@ // visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable // } // } -// } \ No newline at end of file +// } diff --git a/frontend/cypress/support/component-index.html b/frontend/cypress/support/component-index.html index ac6e79f..faf3b5f 100644 --- a/frontend/cypress/support/component-index.html +++ b/frontend/cypress/support/component-index.html @@ -1,12 +1,12 @@ - + - - - + + + Components App
- \ No newline at end of file + diff --git a/frontend/cypress/support/component.ts b/frontend/cypress/support/component.ts index 37f59ed..e11a5fe 100644 --- a/frontend/cypress/support/component.ts +++ b/frontend/cypress/support/component.ts @@ -14,12 +14,12 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') -import { mount } from 'cypress/react18' +import { mount } from "cypress/react18"; // Augment the Cypress namespace to include type definitions for // your custom command. @@ -28,12 +28,12 @@ import { mount } from 'cypress/react18' declare global { namespace Cypress { interface Chainable { - mount: typeof mount + mount: typeof mount; } } } -Cypress.Commands.add('mount', mount) +Cypress.Commands.add("mount", mount); // Example use: -// cy.mount() \ No newline at end of file +// cy.mount() diff --git a/frontend/cypress/support/e2e.ts b/frontend/cypress/support/e2e.ts index f80f74f..6a173d6 100644 --- a/frontend/cypress/support/e2e.ts +++ b/frontend/cypress/support/e2e.ts @@ -14,7 +14,7 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file +// require('./commands') diff --git a/frontend/lib/deletes.tsx b/frontend/lib/deletes.tsx new file mode 100644 index 0000000..201800b --- /dev/null +++ b/frontend/lib/deletes.tsx @@ -0,0 +1,265 @@ +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 vite_backend_url = "https://gather-app-307.azurewebsites.net"; +const token = localStorage.getItem("token"); + +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}`); + } + console.log("group deleted successfully"); + } catch (error) { + console.error("There was an error deleting the group", error); + } +}; + +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}`); + } + console.log("Basket deleted successfully"); + } catch (error) { + 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, + userId: string, +) => { + try { + const response = await fetch( + `${vite_backend_url}/users/${userId}/remove-friend`, + { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ friendId: friendId }), + }, + ); + if (!response.ok) { + throw new Error(`Error: ${response.statusText}`); + } + console.log("Friend removed successfully"); + } catch (error) { + console.error("There was an error removing the friend", error); + } +}; + +export const removeItemFromBasketAndDelete = async ( + baskets: IBasket[], + item: IItem, +) => { + try { + baskets.forEach(async (basket) => { + if (basket.items.includes(item._id)) { + const newItems = basket.items.filter((i: ObjectId) => i !== item._id); + const res = await fetch(`${vite_backend_url}/baskets/${basket._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ items: newItems }), + }); + if (res.status === 200) { + return await fetch(`${vite_backend_url}/items/${item._id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + }); + } + } + }); + } catch (error) { + console.error( + "There was an error removing the item from the basket", + error, + ); + } +}; diff --git a/frontend/lib/edits.tsx b/frontend/lib/edits.tsx new file mode 100644 index 0000000..369ac66 --- /dev/null +++ b/frontend/lib/edits.tsx @@ -0,0 +1,211 @@ +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 { fetchBasket } from "./fetches"; + +// const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; +const vite_backend_url = "https://gather-app-307.azurewebsites.net"; +type updatedGroup = { + groupName: string; + description: string; + privateGroup: string; +}; + +type updatedBasket = { + basketName: string; + description: string; +}; + +type updatedItem = { + name: string; + notes: string; + toShare: string; + isPrivate: string; + price: string; + quantity: string; +}; + +const token = localStorage.getItem("token"); + +export const addGroupToUser = async (user: IUser, groups: string[]) => { + return fetch(`${vite_backend_url}/users/${user._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ groups: groups }), + }); +}; + +export const editGroup = async (groupId: string, groupData: updatedGroup) => { + return fetch(`${vite_backend_url}/groups/${groupId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(groupData), + }); +}; + +export const editBasket = async ( + basketId: string, + 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), + }); +}; + +export const addItemToBasket = async ( + basketId: 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 }), + }); +}; + +export const editItem = async (itemId: string, itemData: updatedItem) => { + return fetch(`${vite_backend_url}/items/${itemId}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(itemData), + }); +}; + +export const moveItem = async ( + userBaskets: IBasket[], + newBasket: IBasket, + item: IItem, +) => { + try { + if (newBasket._id === item.basket) { + console.log("Item already in basket"); + return; + } + console.log(userBaskets, item, newBasket); + const res = await fetchBasket(String(item.basket)); + if (!res.ok) { + console.error("Failed to fetch current basket"); + return; + } else { + const currentBasket = await res.json() as IBasket; + console.log(currentBasket); + const newBasketsItems = currentBasket?.items.filter((i) => i !== item._id); + console.log(newBasketsItems); + const removeItemFromBasket = await fetch( + `${vite_backend_url}/baskets/${item.basket}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + items: newBasketsItems, + }), + }, + ); + if (removeItemFromBasket.ok) { + console.log("Item removed from basket successfully"); + } else { + console.error("Failed to remove item"); + } + const updatedItem = await fetch(`${vite_backend_url}/items/${item._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ basket: newBasket._id }), + }); + if (updatedItem.ok) { + console.log("Item added to basket successfully"); + } else { + console.error("Failed to update item"); + } + const updatedBasket = await fetch( + `${vite_backend_url}/baskets/${newBasket._id}`, + { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ items: [...newBasket.items, item._id] }), + }, + ); + if (updatedBasket.ok) { + console.log("Item added to basket successfully"); + } else { + console.error("Failed to update basket"); + } + } + } catch (error) { + console.error("Error moving item:", error); + } +}; + +export const editUser = async ( + userId: 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), + }); +}; + +export const addBasketToGroup = async (group: IGroup, baskets: ObjectId[]) => { + return fetch(`${vite_backend_url}/groups/${group._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify({ baskets: baskets }), + }); +}; + +export const addUserToGroup = async (group: IGroup, users: ObjectId[]) => { + return fetch(`${vite_backend_url}/groups/${group._id}`, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + 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 }), + }); +} diff --git a/frontend/lib/fetches.tsx b/frontend/lib/fetches.tsx new file mode 100644 index 0000000..9137a41 --- /dev/null +++ b/frontend/lib/fetches.tsx @@ -0,0 +1,266 @@ +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; +const vite_backend_url = "https://gather-app-307.azurewebsites.net"; + +export const fetchBasket = async (basketId: string) => { + 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}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); +}; + +export const fetchGroupById = async (groupId: string) => { + try { + const res = await fetch(`${vite_backend_url}/groups/${groupId}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); + if (res.ok) { + return res.json(); + } else { + throw new Error(`Failed to fetch group: ${res.statusText}`); + } + } catch (err) { + return null; + } +}; + +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}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); +}; + +export const fetchUserWithString = async (userId: string) => { + 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}`, { + headers: { + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + } + ); + if (res.status === 200) { + const data = await res.json(); + return data; + } + }); + + const tempGroupList: IGroup[] = await Promise.all(groupPromises); + return tempGroupList; +}; + +export const fetchUserFriendsByUser = async (user: IUser) => { + const friendPromises = user.friends.map(async (friend: ObjectId) => { + 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; + } + }); + + const tempFriendList: IUser[] = await Promise.all(friendPromises); + return tempFriendList; +}; + +export const addFriendToGroup = async (friendId: ObjectId, groupId: string) => { + try { + const group = await fetchGroupById(groupId); + const res = await fetchUser(friendId); + + 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 addGroupToUser(friend, friend.groups); + if (updatedRes.ok) { + console.log("Friend added to group successfully"); + window.location.reload(); + } else { + console.error("Failed to update user"); + } + } else { + console.log("Friend is already in group"); + } + } else { + console.log("User not Found", res.status); + } + } catch (error) { + console.error("Error adding friend:", error); + } +}; + +export const fetchGroupBaskets = async (group: IGroup) => { + const basketPromises = group.baskets.map(async (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; + } else { + console.log("error"); + } + }); + + const tempBaskets = (await Promise.all(basketPromises)) as IBasket[]; + return tempBaskets; +}; + +export const fetchBasketItems = async (basket: IBasket) => { + if (basket.items.length === 0) { + return []; + } + const itemPromises = basket.items.map(async (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; + } + }); + + const tempItems = await Promise.all(itemPromises); + return tempItems; +}; + +export const fetchUserBaskets = async (userId: string) => { + 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[]; + for (const basket of allBaskets) { + if (basket.members.includes(userId)) { + userBaskets.push(basket); + } + } + return userBaskets; + } + return []; +}; + +export const fetchGroups = async (userGroups: ObjectId[]) => { + const groupPromises = userGroups.map(async (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; + } + }); + + const tempGroupList = await Promise.all(groupPromises); + return tempGroupList.filter((group) => group !== undefined); +}; + +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}`, { + 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) { + console.error(err); + return []; + } +}; + +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), + }); + return res; +}; diff --git a/frontend/lib/posts.tsx b/frontend/lib/posts.tsx new file mode 100644 index 0000000..ff25361 --- /dev/null +++ b/frontend/lib/posts.tsx @@ -0,0 +1,90 @@ +import { ObjectId } from "mongoose"; + +// const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; +const vite_backend_url = "https://gather-app-307.azurewebsites.net"; +console.log("Backend URL:", vite_backend_url); + +type newUser = { + username: string; + email: string; + password: string; + firstName: string; + lastName: string; +}; + +type newItems = { + name: string; + toShare: boolean; + isPrivate: boolean; + type: string; + notes: string; + price: number; + quantity: number; + basket: ObjectId[]; +}; + +type credentials = { + username: string; + password: string; +}; + +type basketData = { + basketName: string; + description: string; + members: ObjectId[]; +}; + +export const createUser = async (user: newUser) => { + return fetch(`${vite_backend_url}/users`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(user), + }); +}; + +export const createNewItem = async (itemData: newItems) => { + return fetch(`${vite_backend_url}/items`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(itemData), + }); +}; + +export const loginUser = async (credentials: credentials) => { + return fetch(`${vite_backend_url}/login`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(credentials), + }); +}; + +export const createNewGroup = async (groupData: any) => { + return fetch(`${vite_backend_url}/groups/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(groupData), + }); +}; + +export const createNewBasket = async (basketData: basketData) => { + return fetch(`${vite_backend_url}/baskets/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(basketData), + }); +}; diff --git a/frontend/public/HomePage.jpg b/frontend/public/HomePage.jpg new file mode 100644 index 0000000..79ef9e4 Binary files /dev/null and b/frontend/public/HomePage.jpg differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 32bd2dd..61a854c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -2,38 +2,28 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import { Box, ChakraProvider } from "@chakra-ui/react"; import { useState, useEffect } from "react"; import LoginPage from "./pages/LoginPage"; -import HomePage from "./pages/HomePage"; 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"; -// TODO: When we integrate the frontend to use the backend, we need to use this API server: gather-app-inv.azurewebsites.net -// fetch("gather-app-inv.azurewebsites.net"); -const getRandomColor = () => { - //prob have to change this later but made for demo - const letters = "0123456789ABCDEF"; - let color = "#"; - for (let i = 0; i < 6; i++) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; -}; +// const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; +const vite_backend_url = "https://gather-app-307.azurewebsites.net"; + +console.log("Backend URL:", vite_backend_url); function App() { const [token, setToken] = useState(localStorage.getItem("token") ?? ""); const [username, setUsername] = useState(""); const getUser = async () => { - if (token !== "") { - const res = await fetch("http://localhost:3001/", { + if (token !== "" && vite_backend_url) { + const res = await fetch(`${vite_backend_url}/`, { method: "GET", headers: { "Content-Type": "application/json", @@ -45,14 +35,14 @@ function App() { console.log(data); setUsername(data.username); const userres = await fetch( - `http://localhost:3001/users/${data.username}`, + `${vite_backend_url}/users/${data.username}`, { method: "GET", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, - } + }, ); if (userres.status === 200) { const user = await userres.json(); @@ -65,56 +55,48 @@ function App() { useEffect(() => { getUser().then(() => { - setLoggedIn(true); + setLoggedIn(!loggedIn); }); }, [token]); const [user, setUser] = useState(null); - const avatarColor = getRandomColor(); const [loggedIn, setLoggedIn] = useState(false); return ( - + {loggedIn && username != "" ? ( ) : ( - + )} - } /> + } /> } /> } - /> - } /> } /> - } />{" "} - {/* added route for individual group page */} } @@ -129,17 +111,11 @@ function App() { } /> } - /> - } - /> - } - /> + path="/groups/:groupId" + element={ + + } + /> diff --git a/frontend/src/components/AddFriendToBasket.tsx b/frontend/src/components/AddFriendToBasket.tsx new file mode 100644 index 0000000..43f656e --- /dev/null +++ b/frontend/src/components/AddFriendToBasket.tsx @@ -0,0 +1,123 @@ +import React, { useState, useEffect } from "react"; +import { + Button, + Popover, + PopoverTrigger, + PopoverContent, + PopoverHeader, + PopoverBody, + PopoverFooter, + PopoverCloseButton, + PopoverArrow, +} from "@chakra-ui/react"; +import { IUser } from "../../../backend/models/userSchema"; +import { fetchBasket } from "../../lib/fetches"; +import { editBasket } from "../../lib/edits"; + +interface Props { + basketId: string; + memberid: IUser[]; + 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, + currentUserId, +}) => { + // Initialize the members state with the filtered memberid prop + const [members, setMembers] = useState(() => + memberid.filter((member) => member._id.toString() !== currentUserId), + ); + + useEffect(() => { + // This effect runs when memberid prop changes + setMembers( + memberid.filter((member) => member._id.toString() !== currentUserId), + ); + }, [memberid, currentUserId]); + + const AddToBasket = async (basketId: string, friendId: string) => { + try { + const res = await fetchBasket(basketId); + let basket; + if (res.ok) { + basket = await res.json(); + if (!basket.members.includes(friendId)) { + basket.members.push(friendId); + console.log("Pushed friend ID to basket's member list"); + const updatedRes1 = await editBasket(basketId, basket); + + if (updatedRes1.ok) { + console.log("Friend added to group's member list successfully"); + } else { + console.error("Failed to update group"); + } + } else { + console.log("Friend is already in basket"); + } + } else { + console.log("Basket not Found"); + } + } catch (error) { + console.error("Error adding friend:", error); + } + window.location.reload(); + }; + + return ( +
+ + + + + + + + Members + +
    + {members.map((member) => ( +
  • + {member.username} + +
  • + ))} +
+
+ +
+
+
+ ); +}; + +export default AddFriendToBasket; diff --git a/frontend/src/components/Basket.tsx b/frontend/src/components/Basket.tsx index b59cee5..e97d648 100644 --- a/frontend/src/components/Basket.tsx +++ b/frontend/src/components/Basket.tsx @@ -1,45 +1,46 @@ import { Avatar, Box, - Button, Divider, Flex, Heading, Text, VStack, } from "@chakra-ui/react"; +import "../styles/Basket.css"; import { useEffect, useState } from "react"; import BasketItem from "./BasketItem"; -import "../styles/Basket.css"; import NewItemOptions from "./NewItemOptions"; - -export interface Basket { - basketName: string; - description: string; - memberIds: string[]; - itemIds: string[]; - created?: Date; -} +import EditBasket from "./EditBasket"; +import AddFriendToBasket from "./AddFriendToBasket"; +import { fetchBasket } 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 }; - isOwnerView: boolean; + groupMembers: IUser[]; + LoggedInUser: IUser | null; + groupId: string; } -const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { - const [basketObj, setBasket] = useState({} as Basket); +const BasketComp = ({ + basketId, + stateObj, + groupMembers, + LoggedInUser, + groupId, +}: Props) => { + const [basketObj, setBasket] = useState({} as IBasket); const [error, setError] = useState({ msg: "", isErrored: false, }); - const fetchItem = () => { - return fetch(`http://localhost:3001/baskets/${basketId}`); - }; - useEffect(() => { - fetchItem() + fetchBasket(basketId) .then((res) => res.status === 200 ? res.json() @@ -47,15 +48,16 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { ) .then((data) => { setBasket({ + _id: data._id, basketName: data.basketName, description: data.description, - itemIds: data.items, - memberIds: data.members, + items: data.items, + members: data.members, created: new Date(data.created), }); }) .catch((err) => { - console.log("Terrible error occured!", err); + console.log("Error: ", err); setError({ msg: err, isErrored: true, @@ -63,9 +65,8 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { }); }, [basketId]); - const memberView = `${basketObj.memberIds === undefined ? "none" : basketObj?.memberIds?.length > 1 ? "auto" : "none"}`; - const groupOwnerView = `${isOwnerView ? "auto" : "none"}`; - const basketMemberView = basketObj?.memberIds?.includes(stateObj?.user?._id); + const memberView = `${basketObj.members === undefined ? "none" : basketObj?.members?.length > 1 ? "auto" : "none"}`; + const basketMemberView = basketObj?.members?.includes(stateObj?.user?._id); return ( { - {basketObj.itemIds !== undefined ? ( + {basketObj.items !== undefined ? ( <> @@ -113,7 +114,7 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { Number of items:{" "} - {basketObj?.itemIds?.length} + {basketObj?.items?.length} Description: @@ -124,33 +125,26 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { - Members:{" "} - {basketObj?.memberIds?.join(", ")} + Members: {basketObj?.members?.join(", ")} - - + + @@ -172,13 +166,13 @@ const BasketComp = ({ basketId, stateObj, isOwnerView }: Props) => { - {basketObj.itemIds !== undefined ? ( - basketObj.itemIds?.map((item) => { + {basketObj ? ( + basketObj.items?.map((item: ObjectId) => { return ( ); }) diff --git a/frontend/src/components/BasketItem.tsx b/frontend/src/components/BasketItem.tsx index d6cc599..94117ad 100644 --- a/frontend/src/components/BasketItem.tsx +++ b/frontend/src/components/BasketItem.tsx @@ -2,6 +2,7 @@ import { Text, Icon, SkeletonText, Box, Flex } 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"; export interface MinimalItem { name: string; @@ -23,13 +24,9 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { isErrored: false, }); - const fetchItem = () => { - return fetch(`http://localhost:3001/items/${itemId}`); - }; - useEffect(() => { setLoading(true); - fetchItem() + fetchItem(itemId) .then((res) => res.status === 200 ? res.json() @@ -62,7 +59,7 @@ const BasketItem = ({ itemId, basketMemberView }: Props) => { } return ( - + {loading ? ( diff --git a/frontend/src/components/CompactItem.tsx b/frontend/src/components/CompactItem.tsx index 1060660..d25a7c9 100644 --- a/frontend/src/components/CompactItem.tsx +++ b/frontend/src/components/CompactItem.tsx @@ -1,5 +1,5 @@ import { - Button, + IconButton, Popover, PopoverContent, PopoverTrigger, @@ -9,38 +9,33 @@ import { PopoverBody, VStack, Box, - IconButton, } from "@chakra-ui/react"; -import { EditIcon } from "@chakra-ui/icons"; - -interface Props { - name: string; - desc: string; - quant: number; - price: number; - pub: boolean; - assigned: boolean; -} +import { EditIcon, SearchIcon } from "@chakra-ui/icons"; +import { IItem } from "../../../backend/models/itemSchema"; -const CompactItem = ({ name, desc, quant, price, pub }: Props) => { +const CompactItem = ({ item }: { item: IItem }) => { // Note: Colors not added yet, just basic structure return ( - + } /> - {name} + {item.name} - Description: {desc} - Quantity: {quant} - Price (per item): {price} - {quant > 1 ? {`\nTotal price: ${price * quant}`} : ""} - Viewability: {pub ? "Public" : "Private"} + Description: {item.notes} + Quantity: {item.quantity} + Price (per item): {item.price} + {item.quantity > 1 ? ( + {`\nTotal price: ${item.price * item.quantity}`} + ) : ( + "" + )} + Viewability: {item.isPrivate ? "Public" : "Private"} } /> diff --git a/frontend/src/components/EditBasket.tsx b/frontend/src/components/EditBasket.tsx index 3fa6185..e067264 100644 --- a/frontend/src/components/EditBasket.tsx +++ b/frontend/src/components/EditBasket.tsx @@ -16,16 +16,23 @@ import { Text, } from "@chakra-ui/react"; import React, { useState, useEffect } from "react"; -import {} from "@chakra-ui/react"; +import { fetchBasket } from "../../lib/fetches"; +import { + handleDeleteAllItemsInBasket, + handleDeleteBasket, + handleDeleteBasketFromGroup, +} from "../../lib/deletes"; +import { editBasket } from "../../lib/edits"; //Add Radio for boolean //Number input for number type 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(""); @@ -39,9 +46,7 @@ const EditBasket: React.FC = ({ basketId }) => { useEffect(() => { const fetchBasketData = async () => { try { - const response = await fetch( - `http://localhost:3001/baskets/${basketId}`, - ); + const response = await fetchBasket(basketId); if (response.ok) { const data = await response.json(); setBasketData({ @@ -62,20 +67,22 @@ const EditBasket: React.FC = ({ basketId }) => { fetchBasketData(); }, [basketId]); - const handleDelete = async () => { + const handleDelete = async (groupId: string, basketId: string) => { + console.log("got here"); + console.log(groupId); + console.log(basketId); try { - const response = await fetch( - `http://localhost:3001/baskets/${basketId}`, - { - method: "DELETE", - }, - ); - if (!response.ok) { - throw new Error(`Error: ${response.statusText}`); - } - console.log("Basket deleted successfully"); + // 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("There was an error deleting the basket", error); + console.error("An error occurred while deleting:", error); } }; @@ -86,16 +93,7 @@ const EditBasket: React.FC = ({ basketId }) => { description: editedDesc, }; console.log(updatedBasket); - const response = await fetch( - `http://localhost:3001/baskets/${basketId}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedBasket), - }, - ); + const response = await editBasket(basketId, updatedBasket); if (response.ok) { setBasketData((prev) => ({ @@ -110,14 +108,13 @@ const EditBasket: React.FC = ({ basketId }) => { } catch (error) { console.error("Error updating profile:", error); } + window.location.reload(); }; return ( - + = ({ basketId }) => { _hover={{ bg: "#ff8366", color: "var(--col-dark)" }} mt={2} ml="auto" - onClick={handleDelete} + onClick={() => handleDelete(groupId, basketId)} > Delete - ) : ( diff --git a/frontend/src/components/EditGroup.tsx b/frontend/src/components/EditGroup.tsx index 9f67437..21431c6 100644 --- a/frontend/src/components/EditGroup.tsx +++ b/frontend/src/components/EditGroup.tsx @@ -20,15 +20,36 @@ import { } from "@chakra-ui/react"; import React, { useState, useEffect } from "react"; import {} from "@chakra-ui/react"; +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(""); @@ -40,11 +61,12 @@ const Editgroup: React.FC = ({ GroupId }) => { groupDesc: "", groupPub: "", }); + const navigate = useNavigate(); useEffect(() => { const fetchgroupData = async () => { try { - const response = await fetch(`http://localhost:3001/groups/${GroupId}`); + const response = await fetchGroupById(GroupId); if (response.ok) { const data = await response.json(); setgroupData({ @@ -67,17 +89,22 @@ const Editgroup: React.FC = ({ GroupId }) => { fetchgroupData(); }, [GroupId]); - const handleDelete = async () => { + const handleDelete = async (groupId: string, userIds: string[]) => { + console.log("here"); + console.log(userIds); try { - const response = await fetch(`http://localhost:3001/groups/${GroupId}`, { - method: "DELETE", - }); - if (!response.ok) { - throw new Error(`Error: ${response.statusText}`); + 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); } - console.log("group deleted successfully"); + navigate("/groups"); } catch (error) { - console.error("There was an error deleting the group", error); + console.error("An error occurred while deleting:", error); } }; @@ -89,13 +116,7 @@ const Editgroup: React.FC = ({ GroupId }) => { privateGroup: editedPub, }; console.log(updatedgroup); - const response = await fetch(`http://localhost:3001/groups/${GroupId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedgroup), - }); + const response = await editGroup(GroupId, updatedgroup); if (response.ok) { setgroupData((prev) => ({ @@ -111,14 +132,13 @@ const Editgroup: React.FC = ({ GroupId }) => { } catch (error) { console.error("Error updating profile:", error); } + window.location.reload(); }; return ( - + = ({ GroupId }) => { _hover={{ bg: "#ff8366", color: "var(--col-dark)" }} mt={2} ml="auto" - onClick={handleDelete} + onClick={() => + GroupId && members && handleDelete(GroupId, members) + } > Delete - ) : ( @@ -259,21 +270,7 @@ const Editgroup: React.FC = ({ GroupId }) => { {" "} {groupData.groupPub === "true" ? "Private" : "Public"} - - - + )} diff --git a/frontend/src/components/EditItem.tsx b/frontend/src/components/EditItem.tsx index 3c272eb..c994fe5 100644 --- a/frontend/src/components/EditItem.tsx +++ b/frontend/src/components/EditItem.tsx @@ -22,9 +22,13 @@ import { Radio, RadioGroup, Text, + IconButton, } from "@chakra-ui/react"; +import { SearchIcon } from "@chakra-ui/icons"; import React, { useState, useEffect } from "react"; import {} from "@chakra-ui/react"; +import { fetchItem } from "../../lib/fetches"; +import { editItem } from "../../lib/edits"; //Add Radio for boolean //Number input for number type @@ -55,7 +59,7 @@ const EditItem: React.FC = ({ itemId }) => { useEffect(() => { const fetchItemData = async () => { try { - const response = await fetch(`http://localhost:3001/items/${itemId}`); + const response = await fetchItem(itemId); if (response.ok) { const data = await response.json(); setItemData({ @@ -87,20 +91,6 @@ const EditItem: React.FC = ({ itemId }) => { const format = (val: any) => `$` + val; const parse = (val: any) => val.replace(/^\$/, ""); - const handleDelete = async () => { - try { - const response = await fetch(`http://localhost:3001/items/${itemId}`, { - method: "DELETE", - }); - 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); - } - }; - const handleSaveChanges = async () => { try { const updatedItem = { @@ -112,13 +102,7 @@ const EditItem: React.FC = ({ itemId }) => { quantity: editedQuant, }; console.log(updatedItem); - const response = await fetch(`http://localhost:3001/items/${itemId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedItem), - }); + const response = await editItem(itemId, updatedItem); if (response.ok) { setItemData((prev) => ({ @@ -134,6 +118,7 @@ const EditItem: React.FC = ({ itemId }) => { } else { console.error("Failed to update profile"); } + window.location.reload(); } catch (error) { console.error("Error updating profile:", error); } @@ -142,9 +127,7 @@ const EditItem: React.FC = ({ itemId }) => { return ( - + } /> = ({ itemId }) => { - Is this sharable? + Sharable? = ({ itemId }) => { - + - + + + + Create new basket + +
+ + + + + + + Error: {errored.msg} + + + +
+ +
+
+ + ); +}; + +export default NewBasketOptions; diff --git a/frontend/src/components/NewGroupOptions.tsx b/frontend/src/components/NewGroupOptions.tsx index 1530dda..ef96267 100644 --- a/frontend/src/components/NewGroupOptions.tsx +++ b/frontend/src/components/NewGroupOptions.tsx @@ -13,6 +13,8 @@ import { import { FormEvent, useState } from "react"; import "../styles/JoinGroup.css"; import { IUser } from "../../../backend/models/userSchema"; +import { createNewGroup } from "../../lib/posts"; +import { addGroupToUser } from "../../lib/edits"; const NewGroupOptions = ({ user, @@ -32,35 +34,20 @@ const NewGroupOptions = ({ privateGroup: boolean, description: string, ) => { - const promise = await fetch("http://localhost:3001/groups/", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify({ - groupName, - privateGroup, - description, - members: [user._id], - }), //dummyUserId will need to be replaced - }); + const groupData = { + groupName, + privateGroup, + description, + members: [user._id], + }; + const promise = await createNewGroup(groupData); if (promise.status === 201) { const data = await promise.json(); console.log("Group created successfully", data); + const newData = [...user.groups, data._id]; - console.log(newData); - const userPromise = await fetch( - `http://localhost:3001/users/${user._id}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify({ groups: newData }), - }, - ); + + const userPromise = await addGroupToUser(user, newData); if (userPromise.status === 200) { const userData = await userPromise.json(); updateUser(userData); diff --git a/frontend/src/components/NewItemOptions.tsx b/frontend/src/components/NewItemOptions.tsx index 0ac14fc..0e217dd 100644 --- a/frontend/src/components/NewItemOptions.tsx +++ b/frontend/src/components/NewItemOptions.tsx @@ -1,7 +1,8 @@ import { - Button, + IconButton, Flex, Box, + Heading, Popover, PopoverCloseButton, PopoverContent, @@ -18,12 +19,18 @@ import { } from "@chakra-ui/react"; import { FormEvent, useState } from "react"; import "../styles/JoinGroup.css"; +import { AddIcon } from "@chakra-ui/icons"; +import { fetchBasket } from "../../lib/fetches"; +import { createNewItem } from "../../lib/posts"; +import { addItemToBasket } from "../../lib/edits"; +import { ObjectId } from "mongoose"; +import { IBasket } from "../../../backend/models/basketSchema"; const NewItemOptions = ({ basket, updateBasket, }: { - basket: any; + basket: string; updateBasket: any; }) => { const createItem = async ( @@ -35,16 +42,10 @@ const NewItemOptions = ({ price: number, quantity: number, ) => { - const res = await fetch(`http://localhost:3001/baskets/${basket}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - }); + const res = await fetchBasket(basket); if (res.ok) { - const currentBasket = await res.json(); + const currentBasket = (await res.json()) as IBasket; const payload = { name, toShare, @@ -56,29 +57,12 @@ const NewItemOptions = ({ basket: [currentBasket._id], }; - const promise = await fetch("http://localhost:3001/items/", { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify(payload), - }); + const promise = await createNewItem(payload); if (promise.status === 201) { const data = await promise.json(); - const newData = [...currentBasket.items, data._id]; - const basketPromise = await fetch( - `http://localhost:3001/baskets/${currentBasket._id}`, - { - method: "PATCH", - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${localStorage.getItem("token")}`, - }, - body: JSON.stringify({ items: newData }), - }, - ); + const newData = [...currentBasket.items, data._id] as ObjectId[]; + const basketPromise = await addItemToBasket(currentBasket._id, newData); if (basketPromise.status === 200) { const basketData = await basketPromise.json(); @@ -86,15 +70,14 @@ const NewItemOptions = ({ } } } + window.location.reload(); }; return ( - + + + Add Item + ); @@ -179,25 +162,20 @@ const CreateItem = ({ postItem }: CreateProps) => { setError({ state: false, msg: "" }); onClose(); }} - placement="bottom-end" + placement="auto" autoFocus closeOnBlur > - + } + colorScheme="teal" + /> = ({ groupId, friends, members }) => { + // Create a set of member IDs for efficient lookup const memberIds = new Set(members.map(member => member._id)); + const memberIds = new Set(members.map((member) => member._id)); + // Initialize the friends state with the filtered friends prop + const [friendsNotInGroup, setFriendsNotInGroup] = useState(() => + friends.filter((friend) => !memberIds.has(friend._id)), + ); + + useEffect(() => { + // This effect runs when friends or members prop changes + const memberIds = new Set(members.map((member) => member._id)); + setFriendsNotInGroup( + friends.filter((friend) => !memberIds.has(friend._id)), + ); + }, [friends, members]); + + // Function to handle button click and log members to the console + const handleLogMembers = () => { + console.log("friends:", friendsNotInGroup); + console.log("friend Users:", friends); + }; + const addToGroup = async (friendId: ObjectId, groupId: string) => { + try { + const group = await fetchGroupById(groupId); + const res = await fetchUser(friendId); + + 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 addGroupToUser(friend, friend.groups); + if (updatedRes.ok) { + console.log("Friend added to group successfully"); + window.location.reload(); + } else { + console.error("Failed to update user"); + } + } else { + console.log("Friend is already in group"); + } + } else { + console.log("User not Found", res.status); + } + } catch (error) { + console.error("Error adding friend:", error); + } + }; + + return ( +
+ + + + + + + + + Friends not in this Group + + + {groupId != undefined && friendsNotInGroup.length > 0 ? ( +
    + {friendsNotInGroup.map((friend) => ( +
  • + {friend.username} + +
  • + ))} +
+ ) : ( +

No friends to add

+ )} +
+ +
+
+
+ ); +}; + +export default SendInviteToGroup; diff --git a/frontend/src/components/UserProfile.tsx b/frontend/src/components/UserProfile.tsx index cec9986..294b1f9 100644 --- a/frontend/src/components/UserProfile.tsx +++ b/frontend/src/components/UserProfile.tsx @@ -15,13 +15,14 @@ import { useClipboard, } from "@chakra-ui/react"; import { CopyIcon } from "@chakra-ui/icons"; +import { fetchUserWithString } from "../../lib/fetches"; +import { editUser } from "../../lib/edits"; interface UserProfileProps { userId: string; - avatarColor: string; } -const UserProfile: React.FC = ({ userId, avatarColor }) => { +const UserProfile: React.FC = ({ userId }) => { const [isEditing, setIsEditing] = useState(false); const [profileData, setProfileData] = useState({ userId: "", @@ -39,7 +40,7 @@ const UserProfile: React.FC = ({ userId, avatarColor }) => { useEffect(() => { const fetchUserProfile = async () => { try { - const response = await fetch(`http://localhost:3001/users/${userId}`); + const response = await fetchUserWithString(userId); if (response.ok) { const data = await response.json(); setProfileData({ @@ -70,13 +71,7 @@ const UserProfile: React.FC = ({ userId, avatarColor }) => { lastName: editedLastName, }; - const response = await fetch(`http://localhost:3001/users/${userId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updatedProfile), - }); + const response = await editUser(userId, updatedProfile); if (response.ok) { setProfileData((prev) => ({ @@ -98,14 +93,16 @@ const UserProfile: React.FC = ({ userId, avatarColor }) => { return ( - - {profileData.firstName} {profileData.lastName}'s Profile - - - {/* {initials} */} {/* it looked weird w initials in the avatar */} - + + + {profileData.firstName} {profileData.lastName}'s Profile + {isEditing ? ( diff --git a/frontend/src/components/WishList.tsx b/frontend/src/components/WishList.tsx deleted file mode 100644 index f7a7858..0000000 --- a/frontend/src/components/WishList.tsx +++ /dev/null @@ -1,9 +0,0 @@ -//PLACEHOLDER!!!!! -import React from "react"; -import { Box } from "@chakra-ui/react"; - -const WishList: React.FC = () => { - return ; -}; - -export default WishList; diff --git a/frontend/src/components/moveLetters.tsx b/frontend/src/components/moveLetters.tsx new file mode 100644 index 0000000..29bc3db --- /dev/null +++ b/frontend/src/components/moveLetters.tsx @@ -0,0 +1,293 @@ +import React, { useEffect, useRef, useState } from "react"; +import "../styles/moveLetters.css"; +import { Button } from "@chakra-ui/react"; +import imageSrc from "../../public/TheLeaf.png"; + +const MoveLetters: React.FC = () => { + const letters = + "GATHERGATHERGATHEGATHERGATHERGATHERGATHERGATHERGATHEGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHERGATHER".split( + "", + ); + const [showImage, setShowImage] = useState(false); // State to control image display + + const sceneRef = useRef(null); + const [positions, setPositions] = useState< + { x: number; y: number; vx: number; vy: number; settled: boolean }[] + >([]); + + const mousePosition = useRef<{ x: number; y: number }>({ + x: window.innerWidth / 2, + y: window.innerHeight / 2, + }); + const [showText, setShowText] = useState(false); + const animationFrameId = useRef(); + + useEffect(() => { + // Initialize positions for each letter + const initialPositions = letters.map(() => ({ + x: Math.random() * window.innerWidth, + y: Math.random() * window.innerHeight, + vx: 0, + vy: 0, + settled: false, + })); + setPositions(initialPositions); + + // Function to handle collisions and update positions + const updatePositions = () => { + setPositions((prevPositions) => { + // Copy previous positions + const newPositions = [...prevPositions]; + + // Update positions based on mouse interaction + newPositions.forEach((pos, index) => { + if (!pos.settled) { + let newX = pos.x + pos.vx; + let newY = pos.y + pos.vy; + + // Check boundaries + const letterWidth = 20; // Assuming 50px width for letters + const letterHeight = 30; // Assuming 50px height for letters + const minX = 20; + const maxX = window.innerWidth - letterWidth; + const minY = 90; + const maxY = window.innerHeight - letterHeight; + + if (newX < minX) { + newX = minX; + pos.vx *= -1; // Reverse horizontal velocity to bounce off the left border + } else if (newX > maxX) { + newX = maxX; + pos.vx *= -1; // Reverse horizontal velocity to bounce off the right border + } + + if (newY < minY) { + newY = minY; + pos.vy *= -1; // Reverse vertical velocity to bounce off the top border + } else if (newY > maxY) { + newY = maxY; + pos.vy *= -1; // Reverse vertical velocity to bounce off the bottom border + } + + // Update position + newPositions[index] = { ...pos, x: newX, y: newY }; + + // Check if letter is close to button and mark it as settled + const buttonRect = sceneRef.current?.getBoundingClientRect(); + if (buttonRect) { + const distanceToButton = Math.sqrt( + Math.pow(newX - (buttonRect.left + buttonRect.width / 2), 2) + + Math.pow(newY - (buttonRect.top + buttonRect.height / 2), 2), + ); + if (distanceToButton < 150) { + // Adjust the distance as needed + newPositions[index] = { ...pos, settled: true }; + } + } + } + }); + + return newPositions.filter((pos) => !pos.settled); + }); + + animationFrameId.current = requestAnimationFrame(updatePositions); + }; + + // Start the animation loop + animationFrameId.current = requestAnimationFrame(updatePositions); + + // Cleanup + return () => { + if (animationFrameId.current) { + cancelAnimationFrame(animationFrameId.current); + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // Update mouse position + const handleMouseMove = (event: React.MouseEvent) => { + mousePosition.current = { x: event.clientX, y: event.clientY }; + + // Reset velocities to zero + setPositions((prevPositions) => { + return prevPositions.map((pos) => ({ ...pos, vx: 0, vy: 0 })); + }); + + // Influence the velocity of letters based on mouse movement + setPositions((prevPositions) => { + const newPositions = [...prevPositions]; + newPositions.forEach((pos) => { + const dx = pos.x - mousePosition.current.x; + const dy = pos.y - mousePosition.current.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const speed = 1.0; // Adjust the speed as needed + + if (distance < 800) { + // Adjust the range as needed + pos.vx = (dx / distance) * speed; + pos.vy = (dy / distance) * speed; + } + }); + return newPositions; + }); + }; + + const handleMouseMovePull = () => { + // Reset velocities to zero + setPositions((prevPositions) => { + return prevPositions.map((pos) => ({ ...pos, vx: 0, vy: 0 })); + }); + + // Influence the velocity of letters based on mouse movement + setPositions((prevPositions) => { + const newPositions = [...prevPositions]; + newPositions.forEach((pos) => { + const dx = mousePosition.current.x - pos.x; + const dy = mousePosition.current.y - pos.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const speed = 7.0; // Adjust the speed as needed + + if (distance < 5000) { + // Adjust the range as needed + pos.vx = (dx / distance) * speed; + pos.vy = (dy / distance) * speed; + } + }); + return newPositions; + }); + }; + + const handleButtonClick = () => { + setShowText(true); // Show the new text when the button is clicked + console.log(showText); + setTimeout(() => { + setShowImage(true); + }, 3000); + // Toggle to show the image when button is clicked + handleMouseMovePull(); // Perform the original button action + }; + + return ( +
+
+ {positions.map(({ x, y }, index) => ( +
+ {letters[index]} +
+ ))} +
+
+ {!showImage && ( + + )} + {showImage && ( + Descriptive Text + )} +
+ {showText && ( +
+ THE WORLD WITH PERFECT ORGANIZATION +
+ )} + {showText && ( +
+
+ {/* Arrow */} +
+ {/* Text */} +
+ It starts here! +
+
+
+ )} +
+ ); +}; +export default MoveLetters; diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx index 7fbab7f..da65a7e 100644 --- a/frontend/src/pages/HomePage.tsx +++ b/frontend/src/pages/HomePage.tsx @@ -13,6 +13,7 @@ const HomePage = () => { fontSize: "24px", padding: "0 20px", flex: 1, + textAlign: "center", }} >

Welcome to Gather!

diff --git a/frontend/src/pages/IndividualGroupPage.tsx b/frontend/src/pages/IndividualGroupPage.tsx index 34556b6..923ebae 100644 --- a/frontend/src/pages/IndividualGroupPage.tsx +++ b/frontend/src/pages/IndividualGroupPage.tsx @@ -17,53 +17,130 @@ import { import { IoArrowBack, IoSearch } from "react-icons/io5"; import { IGroup } from "../../../backend/models/groupSchema"; import { IUser } from "../../../backend/models/userSchema"; +import { IBasket } from "../../../backend/models/basketSchema"; +import { + fetchMembers, + fetchGroupById, + fetchGroupBaskets, + fetchUser, +} from "../../lib/fetches"; +import BasketComp from "../components/Basket"; +import Editgroup from "../components/EditGroup"; +import NewBasketOptions from "../components/NewBasketOptions"; +import SendInviteToGroup from "../components/SendInvite"; +import { fetchUserWithString } from "../../lib/fetches"; + +// const vite_backend_url = import.meta.env.VITE_BACKEND_URL as string; +const vite_backend_url = "https://gather-app-307.azurewebsites.net"; + + +type Props = { + LoggedInUser: IUser | null; + setUser: any; +}; -function IndividualGroupPage() { +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); const [loading, setLoading] = useState(true); 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 fetchGroup = async () => { + const fetchFriends = async (friendIds: string[]) => { try { - const fetchedGroup = await fetch( - `http://localhost:3001/groups/${groupId}`, + const fetchedFriends = await Promise.all( + friendIds.map(async (friendId) => { + const res = await fetchUserWithString(friendId); + if (res.ok) { + return res.json(); + } else { + throw new Error(`Failed to fetch friends: ${res.statusText}`); + } + }), ); - if (fetchedGroup.ok) { - const data = await fetchedGroup.json(); - setGroup(data); - fetchMembers(data.members); - setLoading(false); - } else { - throw new Error(`Failed to fetch group: ${fetchedGroup.statusText}`); - } + + setFriends(fetchedFriends); } catch (err) { console.error(err); } }; - const fetchMembers = async (memberIds: string[]) => { + const fetchUsersFriends = async () => { + if (!LoggedInUser) { + return; + } try { - const fetchedMembers = await Promise.all( - memberIds.map(async (memberId) => { - const res = await fetch(`http://localhost:3001/users/${memberId}`); - if (res.ok) { - return res.json(); - } else { - throw new Error(`Failed to fetch user: ${res.statusText}`); - } - }), - ); - setMembers(fetchedMembers); + const fetchedUser = await fetchUser(LoggedInUser._id); + if (fetchedUser.ok) { + const data = await fetchedUser.json(); + fetchFriends(data.friends); + } else { + throw new Error(`Failed to fetch User: ${fetchedUser.statusText}`); + } } catch (err) { console.error(err); } }; + const fetchGroup = async (groupId: string) => { + const groupData = await fetchGroupById(groupId); + setGroup(groupData); + fetchUsersFriends(); + return groupData; + }; + + const fetchGroupMembers = async (group: IGroup) => { + const membersData = await fetchMembers(group.members); + setMembers(membersData); + }; + + const fetchBaskets = async (group: IGroup) => { + const basketsData = await fetchGroupBaskets(group); + setGroupBaskets(basketsData); + }; + useEffect(() => { - fetchGroup(); - }, [groupId]); + console.log(`Loading: ${loading}`); + if (!localStorage.getItem("token")) { + navigate("/login"); + } + if (groupId) { + fetchGroup(String(groupId)) + .then((group) => { + console.log(`Fetched group: ${group}`); + fetchGroupMembers(group as IGroup) + .then(() => { + console.log(`Fetched group members: ${members}`); + fetchBaskets(group as IGroup) + .then(() => { + console.log(`Fetched group baskets: ${groupBaskets}`); + setLoading(false); + }) + .catch((err) => { + console.log(`Error fetching group baskets: ${err}`); + }); + }) + .catch((err) => { + console.log(`Error fetching group members: ${err}`); + }); + }) + .catch((err) => { + console.log(`Terrible error occurred! ${err}`); + }); + } + }, [loading]); return ( - + } /> {loading ? ( @@ -133,30 +204,35 @@ function IndividualGroupPage() { width="99%" padding="20px" borderWidth="1px" - borderRadius="md" + borderRadius="2xl" backgroundColor="rgba(255, 255, 255, 0.8)" + overflow="auto" > - + + {group.groupName} - + + {groupId ? ( + + ) : ( + <> + )} + - + Members - - {members.map((member) => ( - - - {member.username} - - ))} - - - - - Created On - - {new Date(group.created).toLocaleDateString()} - - - - Description - - - {group.description || "No description given"} - - - - - - - Baskets Component - This is where the Baskets component will go! - - {/* Replace with actual basket items */} - - - Basket Item 1 - - - Basket Item 2 - - - Basket Item 3 + + {members ? ( + members.map((member) => ( + + + {member.username} + + )) + ) : ( + No members found + )} + + + + + Created On + + + {new Date(group.created).toLocaleDateString()} + + + + + Description + + + {group.description || "No description given"} + + + + + + Baskets + + + + {groupBaskets && members ? ( + groupBaskets.map( + (basket) => ( + console.log(group), + console.log(basket), + ( + + ) + ), + ) + ) : ( + No baskets available + )} + + @@ -239,6 +340,6 @@ function IndividualGroupPage() { ); -} +}; export default IndividualGroupPage; diff --git a/frontend/src/pages/ItemsPage.tsx b/frontend/src/pages/ItemsPage.tsx index ce022fc..2f5b373 100644 --- a/frontend/src/pages/ItemsPage.tsx +++ b/frontend/src/pages/ItemsPage.tsx @@ -14,6 +14,7 @@ import ItemGroup from "../components/ItemGroup"; import { useNavigate } from "react-router-dom"; import { IUser } from "../../../backend/models/userSchema"; import { IGroup } from "../../../backend/models/groupSchema"; +import { fetchUserGroupsByUser } from "../../lib/fetches"; type Props = { stateVariable: { @@ -36,17 +37,7 @@ const ItemsPage: React.FC = ({ }; const fetchGroups = async () => { - const groupPromises = stateVariable.user.groups.map( - async (group: string) => { - const res = await fetch(`http://localhost:3001/groups/${group}`); - if (res.status === 200) { - const data = await res.json(); - return data; - } - }, - ); - - const tempGroupList = await Promise.all(groupPromises); + const tempGroupList = await fetchUserGroupsByUser(stateVariable.user); setGroupList(tempGroupList); }; @@ -59,11 +50,15 @@ const ItemsPage: React.FC = ({ .catch((err) => { console.log(`Terrible error occurred! ${err}`); }); + } else { + if (!stateVariable.token) { + navigate("/login"); + } } }, [stateVariable.user]); return ( - + diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index beb9ed8..94f8b88 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -14,6 +14,7 @@ import { import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; +import { loginUser } from "../../lib/fetches"; const LoginPage = ({ updateState }: { updateState: any }) => { const [username, setUsername] = useState(""); @@ -23,27 +24,27 @@ 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; } else { - const res = await fetch("http://localhost:3001/login", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ username, password }), - }); - if (res.status === 200) { - const data = await res.json(); - updateState.setToken(data.token); - updateState.setUser(data.existingUser); - localStorage.setItem("token", data.token); - console.log("Login successful!"); - navigate("/"); - } else { - const err = await res.text(); - console.log("Login failed:", err); + try { + const res = await loginUser({ username, password }); + if (res.status === 200) { + const data = await res.json(); + updateState.setToken(data.token); + updateState.setUser(data.existingUser); + localStorage.setItem("token", data.token); + console.log("Login successful!"); + navigate("/"); + } else { + const err = await res.text(); + console.log("Login failed:", err); + } + } catch (error) { + console.error("Error during login:", error); } } }; diff --git a/frontend/src/pages/MyGroupsPage.tsx b/frontend/src/pages/MyGroupsPage.tsx index ea58a74..6c14a02 100644 --- a/frontend/src/pages/MyGroupsPage.tsx +++ b/frontend/src/pages/MyGroupsPage.tsx @@ -13,11 +13,12 @@ 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"; type Props = { stateVariable: { @@ -37,13 +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); } - + const fetchGroups = async () => { const groupPromises = stateVariable.user.groups.map( async (group: string) => { @@ -58,7 +59,7 @@ const GroupPage: React.FC = ({ const tempGroupList = await Promise.all(groupPromises); setGroupList(tempGroupList); }; - + const searchGroups = (input: string) => { if (input === "") { setFilteredGroups(groupList); @@ -74,14 +75,18 @@ const GroupPage: React.FC = ({ useEffect(() => { if (stateVariable.user) { - fetchGroups() - .then(() => { + fetchGroups(stateVariable.user.groups) + .then((tempGroupList) => { + setGroupList(tempGroupList); setFilteredGroups(groupList); // Initialize with full list - setLoading(false); }) .catch((err) => { console.log(`Terrible error occurred! ${err}`); }); + } else { + if (!stateVariable.token) { + navigate("/login"); + } } }, [stateVariable.user]); @@ -221,8 +226,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 238ea9c..a7c3551 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -1,36 +1,42 @@ -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 WishList from "../components/WishList"; +import { useNavigate } from "react-router-dom"; interface ProfilePageProps { LoggedInUser: string; - avatarColor: string; } const ProfilePage: React.FC = ({ LoggedInUser, - avatarColor, }) => { + const navigate = useNavigate(); + useEffect(() => { + if (!localStorage.getItem("token")) { + navigate("/login"); + } + } + ); + return ( - + - - + + - + @@ -41,12 +47,6 @@ const ProfilePage: React.FC = ({ - - - WishList - - {/* This is a dummy component */} - diff --git a/frontend/src/pages/SignupPage.tsx b/frontend/src/pages/SignupPage.tsx index b0846ef..3a787ad 100644 --- a/frontend/src/pages/SignupPage.tsx +++ b/frontend/src/pages/SignupPage.tsx @@ -15,6 +15,7 @@ import { import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { ViewIcon, ViewOffIcon } from "@chakra-ui/icons"; +import { createUser, loginUser } from "../../lib/posts"; const SignupPage = ({ stateVariable, @@ -50,47 +51,34 @@ const SignupPage = ({ alert("Passwords do not match"); return; } else { - fetch("http://localhost:3001/users", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - username: username, - email: email, - password: password, - firstName: firstName, - lastName: lastName, - }), - }).then(async (res) => { + try { + const user = { firstName, lastName, username, email, password }; + const res = await createUser(user); if (res.status === 201) { const data = await res.json(); updateState.setToken(data.token); updateState.setUser(data.newUser); console.log(stateVariable); console.log("Account created successfully!"); - const login = await fetch("http://localhost:3001/login", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ username, password }), - }); - if (login.status === 200) { - const data = await login.json(); - updateState.setToken(data.token); - localStorage.setItem("token", data.token); + + const loginRes = await loginUser({ username, password }); + if (loginRes.status === 200) { + const loginData = await loginRes.json(); + updateState.setToken(loginData.token); + localStorage.setItem("token", loginData.token); console.log("Login successful!"); navigate("/"); } else { - const err = await res.text(); - console.log("Login failed:", err); + const loginErr = await loginRes.text(); + console.log("Login failed:", loginErr); } } else { const err = await res.text(); console.log("Account creation failed:", err); } - }); + } catch (error) { + console.error("Error during form submission:", error); + } } }; diff --git a/frontend/src/styles/Basket.css b/frontend/src/styles/Basket.css index a5eadd6..935e3b8 100644 --- a/frontend/src/styles/Basket.css +++ b/frontend/src/styles/Basket.css @@ -1,7 +1,7 @@ .basket { border-color: #c1c1c1; border-width: 5px; - overflow: hidden; + overflow: scroll; color: var(--col-secondary); } diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index 72909b3..09b01ba 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -4,7 +4,6 @@ font-weight: 400; color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); background-color: #242424; font-synthesis: none; diff --git a/frontend/src/styles/moveLetters.css b/frontend/src/styles/moveLetters.css new file mode 100644 index 0000000..e35f757 --- /dev/null +++ b/frontend/src/styles/moveLetters.css @@ -0,0 +1,55 @@ +/* Resetting margins, padding, and overflow behavior on the entire document */ +html, body { + margin: 0; + padding: 0px; + height: 100%; /* or 100vh */ + overflow-x: hidden; + overflow-y: hidden; + width: 100%; /* Ensures the body takes full width */ +} + +.letterwrapper { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + position: relative; /* This will serve as a stacking context */ + z-index: 0; /* Base layer */ +} + +.letter-container { + position: fixed; + top: 65px; + left: 0; + width: 100%; + height: 100%; + pointer-events: auto; + background-image: url("../../public/HomePage.jpg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + z-index: 1; /* Above the base layer */ +} + +.letter { + position: fixed; + transform: translate(-50%, -50%); + font-size: 50px; + color: #ffffff; + + z-index: 2; /* Ensures letters are above the background container */ +} + +.letter-center-button { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 3; /* Button above letters for interactivity */ +} + +/* Ensure the new text is visible by placing it on top */ +div[style*="text-align: center; marginTop:"] { + z-index: 4; /* Highest in the hierarchy to ensure visibility */ + position: relative; /* Adjust as needed for visibility */ +} diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts new file mode 100644 index 0000000..8e70f71 --- /dev/null +++ b/frontend/src/theme.ts @@ -0,0 +1,15 @@ +// theme.ts + +// 1. import `extendTheme` function +import { extendTheme, type ThemeConfig } from "@chakra-ui/react"; + +// 2. Add your color mode config +const config: ThemeConfig = { + initialColorMode: "light", + useSystemColorMode: false, +}; + +// 3. extend the theme +const theme = extendTheme({ config }); + +export default theme; diff --git a/package.json b/package.json index 3b32f7d..c99de70 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "test": "echo \"Error: no test specified\" && exit 0", "cyp": "cd frontend && cypress open", "dev": "npm -w backend run dev && npm -w frontend run dev", + "start": "cd backend && node --loader ts-node/esm ./index.ts", "front": "npm -w frontend run dev", "back": "npm -w backend run dev", "front lint": "npm -w backend run lint",