diff --git a/backend/src/middleware/authMiddleware.ts b/backend/src/middleware/authMiddleware.ts new file mode 100644 index 0000000..6c143d4 --- /dev/null +++ b/backend/src/middleware/authMiddleware.ts @@ -0,0 +1,119 @@ +import { Request, Response, NextFunction } from "express"; +import { SAMLUser } from "../models/People"; +import { CourseReviews } from "../models/Courses"; +import { HousingReviews } from "../models/Housing"; + +export const isAuthenticated = async ( + req: Request, + res: Response, + next: NextFunction +) => { + // Check if user is in session + if (!(req.session as any).user) { + res.status(401).json({ message: "Authentication required" }); + return; + } + + next(); +}; + +export const isAdmin = async ( + req: Request, + res: Response, + next: NextFunction +) => { + // First check if user is authenticated + if (!(req.session as any).user) { + res.status(401).json({ message: "Authentication required" }); + return; + } + + const azureId = (req.session as any).user.id; + + try { + // Find the user in the database + const user = await SAMLUser.findOne({ id: azureId }); + + // Check if user exists and is an admin + if (!user || !user.isAdmin) { + res.status(403).json({ message: "Admin access required" }); + return; + } + + // User is authenticated and is an admin + next(); + } catch (error) { + console.error("Admin verification error:", error); + res.status(500).json({ message: "Server error" }); + return; + } +}; + +export const isCourseReviewOwner = async ( + req: Request, + res: Response, + next: NextFunction +) => { + // First check if user is authenticated + if (!(req.session as any).user) { + res.status(401).json({ message: "Authentication required" }); + return; + } + // First check if user is authenticated and get the user ID from session + const sessionUserEmail = (req.session as any).user.email; + if (!sessionUserEmail) { + res.status(401).json({ message: "Authentication required" }); + return; + } + + const { reviewId } = req.params; + + const review = await CourseReviews.findOne({ id: reviewId }); + + if (!review) { + res.status(404).json({ message: "Review not found" }); + return; + } + + if (review.user_email != sessionUserEmail) { + res.status(403).json({ + message: "You are not authorized to modify this review", + }); + return; + } + + next(); +}; + +export const isHousingReviewOwner = async (req: Request, res: Response, next: NextFunction) => { + // First check if user is authenticated + if (!(req.session as any).user) { + res.status(401).json({ message: "Authentication required" }); + return; + } + + // First check if user is authenticated and get the user ID from session + const sessionUserEmail = (req.session as any).user.email; + if (!sessionUserEmail) { + res.status(401).json({ message: "Authentication required" }); + return; + } + + const { reviewId } = req.params; + + const review = await HousingReviews.findOne({ id: reviewId }); + + if (!review) { + res.status(404).json({ message: "Review not found" }); + return; + } + + if (review.user_email != sessionUserEmail) { + res.status(403).json({ + message: "You are not authorized to modify this review", + }); + return; + } + + next(); +}; \ No newline at end of file diff --git a/backend/src/routes/CoursesRoutes.ts b/backend/src/routes/CoursesRoutes.ts index 77b3c05..2cdf0a6 100644 --- a/backend/src/routes/CoursesRoutes.ts +++ b/backend/src/routes/CoursesRoutes.ts @@ -1,5 +1,10 @@ import express, { Request, Response } from "express"; import { Courses, CourseReviews } from "../models/Courses"; +import { + isAdmin, + isAuthenticated, + isCourseReviewOwner, +} from "../middleware/authMiddleware"; import { Instructors } from "../models/People"; const router = express.Router(); @@ -10,7 +15,7 @@ const router = express.Router(); * @access Public */ // Courses routes -router.get("/", async (req: Request, res: Response) => { +router.get("/", isAuthenticated, async (req: Request, res: Response) => { try { const { search, number, schools, limit = 50 } = req.query; @@ -56,7 +61,7 @@ router.get("/", async (req: Request, res: Response) => { * @desc Get course by ID * @access Public */ -router.get("/:id", async (req: Request, res: Response) => { +router.get("/:id", isAuthenticated, async (req: Request, res: Response) => { try { const courseId = parseInt(req.params.id, 10); @@ -84,8 +89,7 @@ router.get("/:id", async (req: Request, res: Response) => { * @desc Create new course * @access Private (Admin) */ -router.post("/", async (req: Request, res: Response) => { - // ? Do we need to create slugs for all courses here ? // +router.post("/", isAdmin, async (req: Request, res: Response) => { try { const { id, @@ -133,7 +137,7 @@ router.post("/", async (req: Request, res: Response) => { * @desc Update course * @access Private (Admin) */ -router.put("/:id", async (req: Request, res: Response) => { +router.put("/:id", isAdmin, async (req: Request, res: Response) => { try { const courseId = parseInt(req.params.id, 10); @@ -168,7 +172,7 @@ router.put("/:id", async (req: Request, res: Response) => { * @desc Delete course * @access Private (Admin) */ -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", isAdmin, async (req: Request, res: Response) => { try { const courseId = parseInt(req.params.id, 10); @@ -199,56 +203,64 @@ router.delete("/:id", async (req: Request, res: Response) => { * @desc Get all reviews for a specific course * @access Public */ -router.get("/:id/reviews", async (req: Request, res: Response) => { - try { - const courseId: number = parseInt(req.params.id); - - if (isNaN(courseId)) { - res.status(400).json({ message: "Invalid course ID format" }); - return; +router.get( + "/:id/reviews", + isAuthenticated, + async (req: Request, res: Response) => { + try { + const courseId: number = parseInt(req.params.id); + + if (isNaN(courseId)) { + res.status(400).json({ message: "Invalid course ID format" }); + return; + } + + const reviews = await CourseReviews.find({ course_id: courseId }) + .sort({ updatedAt: -1 }) // -1 for descending order (newest first) + .exec(); + + res.json(reviews); + } catch (err) { + console.error(err); + res.status(500).json({ message: "Server error" }); } - - const reviews = await CourseReviews.find({ course_id: courseId }) - .sort({ updatedAt: -1 }) // -1 for descending order (newest first) - .exec(); - - res.json(reviews); - } catch (err) { - console.error(err); - res.status(500).json({ message: "Server error" }); } -}); +); /** * @route POST /api/courses/:courseId/reviews * @desc Add a new review for a course + * @access Public */ -router.post("/:courseId/reviews", async (req: Request, res: Response) => { - try { - // need to find new max id for the new review - const result = await CourseReviews.aggregate([ - { - $group: { - _id: null, // No need to group, so _id is null - maxValue: { $max: "$id" }, // Find the max value of fieldName +router.post( + "/:courseId/reviews", + isAuthenticated, + async (req: Request, res: Response) => { + try { + // need to find new max id for the new review + const result = await CourseReviews.aggregate([ + { + $group: { + _id: null, // No need to group, so _id is null + maxValue: { $max: "$id" }, // Find the max value of fieldName + }, }, - }, - ]); + ]); - const maxId = result[0].maxValue + 1; + const maxId = result[0].maxValue + 1; - const { courseId } = req.params; + const { courseId } = req.params; - // parse review fields from request - const { - overall, - challenge, - inclusivity, - workPerWeek, - instructorId, - comments, - email, - } = req.body; + // parse review fields from request + const { + overall, + challenge, + inclusivity, + workPerWeek, + instructorId, + comments, + email, + } = req.body; // construct review data const reviewData = { @@ -263,32 +275,37 @@ router.post("/:courseId/reviews", async (req: Request, res: Response) => { user_email: email, }; - const review = new CourseReviews(reviewData); - await review.save(); + const review = new CourseReviews(reviewData); + await review.save(); - res.status(201).json({ message: "Review saved successfully" }); - } catch (error) { - res.status(400).json({ message: "Error creating review" }); + res.status(201).json({ message: "Review saved successfully" }); + } catch (error) { + res.status(400).json({ message: "Error creating review" }); + } } -}); +); /** * @route PATCH /api/courses/reviews/:reviewId * @desc Edit a course review + * @access Owner */ -router.patch("/reviews/:reviewId", async (req: Request, res: Response) => { - try { - const reviewId = req.params.reviewId; - - // parse review fields from request - const { - overall, - challenge, - inclusivity, - workPerWeek, - comments, - instructorId, - } = req.body; +router.patch( + "/reviews/:reviewId", + isCourseReviewOwner, + async (req: Request, res: Response) => { + try { + const reviewId = req.params.reviewId; + + // parse review fields from request + const { + overall, + challenge, + inclusivity, + workPerWeek, + comments, + instructorId, + } = req.body; const updateData = { overall_rating: overall, @@ -299,76 +316,89 @@ router.patch("/reviews/:reviewId", async (req: Request, res: Response) => { ...(instructorId && { instructor_id: instructorId }), }; - const updatedReview = await CourseReviews.findOneAndUpdate( - { id: reviewId }, - updateData, - { new: true } - ); - - if (!updatedReview) { - res.status(404).json({ message: "Review not found" }); + const updatedReview = await CourseReviews.findOneAndUpdate( + { id: reviewId }, + updateData, + { new: true } + ); + + if (!updatedReview) { + res.status(404).json({ message: "Review not found" }); + } + + res.status(200).json({ + message: "Review updated", + updatedReview, + }); + } catch (error) { + console.error("update error: ", error); + res.status(400).json({ message: "Error updating review" }); } - - res.status(200).json({ - message: "Review updated", - updatedReview, - }); - } catch (error) { - console.error("update error: ", error); - res.status(400).json({ message: "Error updating review" }); } -}); +); /** * @route DELETE /api/courses/reviews/:reviewId * @desc Delete a course review + * @access Owner */ -router.delete("/reviews/:reviewId", async (req: Request, res: Response) => { - try { - const review = await CourseReviews.findOneAndDelete({ - id: req.params.reviewId, - }); - - if (!review) { - res.status(404).json({ message: "Review not found" }); +router.delete( + "/reviews/:reviewId", + isCourseReviewOwner, + async (req: Request, res: Response) => { + try { + const review = await CourseReviews.findOneAndDelete({ + id: req.params.reviewId, + }); + + if (!review) { + res.status(404).json({ message: "Review not found" }); + } + + res.status(200).json({ message: "Review deleted" }); + } catch (error) { + res.status(500).json({ message: "Server error" }); } - - res.status(200).json({ message: "Review deleted" }); - } catch (error) { - res.status(500).json({ message: "Server error" }); } -}); +); /** * @route GET /api/courses/:courseId/instructors * @desc Get all previous instructors for a course + * @access Public */ -router.get("/:courseId/instructors", async (req: Request, res: Response) => { - try { - const courseId: number = parseInt(req.params.courseId); - - if (isNaN(courseId)) { - res.status(400).json({ message: "Invalid course ID format" }); - return; - } - - const course = await Courses.findOne({ id: courseId }); - - if (!course) { - res.status(400).json({ message: "No course found" }); - return; +router.get( + "/:courseId/instructors", + isAuthenticated, + async (req: Request, res: Response) => { + try { + const courseId: number = parseInt(req.params.courseId); + + if (isNaN(courseId)) { + res.status(400).json({ message: "Invalid course ID format" }); + return; + } + + const course = await Courses.findOne({ id: courseId }); + + if (!course) { + res.status(400).json({ message: "No course found" }); + return; + } + + const instructorIds = course.all_instructor_ids; + + const instructors = await Instructors.find({ + id: { $in: instructorIds }, + }); + + res.json(instructors); + } catch (error) { + res.status(400).json({ + message: "Error getting course instructors", + }); } - - const instructorIds = course.all_instructor_ids; - - const instructors = await Instructors.find({ - id: { $in: instructorIds }, - }); - - res.json(instructors); - } catch (error) { - res.status(400).json({ message: "Error getting course instructors" }); } -}); +); export default router; diff --git a/backend/src/routes/HousingRoutes.ts b/backend/src/routes/HousingRoutes.ts index ad35a62..06b3662 100644 --- a/backend/src/routes/HousingRoutes.ts +++ b/backend/src/routes/HousingRoutes.ts @@ -7,14 +7,24 @@ import { } from "../models/Housing"; import { housingReviewPictures } from "../server"; import { ObjectId } from 'mongodb'; +import { + isAdmin, + isAuthenticated, + isCourseReviewOwner, + isHousingReviewOwner +} from "../middleware/authMiddleware"; const router = express.Router(); const storage = multer.memoryStorage(); const upload = multer({ storage: storage }); -// Get all buildings -router.get("/", async (req: Request, res: Response) => { +/** + * @route GET /api/campus/housing + * @desc Get all housing buildings + * @access Public + */ +router.get("/", isAuthenticated, async (req: Request, res: Response) => { try { const buildings = await HousingBuildings.find({}); res.json(buildings); @@ -23,8 +33,12 @@ router.get("/", async (req: Request, res: Response) => { } }); -// Get a building by id -router.get("/:building", async (req: Request, res: Response) => { +/** + * @route GET /api/campus/housing/:building + * @desc Get housing building by id + * @access Public + */ +router.get("/:building", isAuthenticated, async (req: Request, res: Response) => { try { // Get building id const buildingId = parseInt(req.params.building, 10); @@ -49,33 +63,12 @@ router.get("/:building", async (req: Request, res: Response) => { } }); -// Get suites in a building -// TODO: This route is not used since we don't have data for suites, consider removing -// router.get("/:building/suites", async (req: Request, res: Response) => { -// try { -// // Get building id -// const { building } = req.params; -// if (!building) { -// res.status(404).json({ message: "No building id provided" }); -// return; -// } - -// // Get suites -// const suites = await HousingSuites.find({ -// housing_building_id: building, -// }); -// if (!suites || suites.length === 0) { -// res.status(404).json({ message: "Suites not found" }); -// return; -// } -// res.json(suites); -// } catch (error) { -// res.status(500).json({ message: "Server error" }); -// } -// }); - -// Get all rooms in a building (by building id) -router.get("/:building/rooms", async (req: Request, res: Response) => { +/** + * @route GET /campus/housing/:building/rooms + * @desc Get all roms in a building (by building id) + * @access Public + */ +router.get("/:building/rooms", isAuthenticated, async (req: Request, res: Response) => { try { // Get building id const buildingId = parseInt(req.params.building, 10); @@ -102,8 +95,12 @@ router.get("/:building/rooms", async (req: Request, res: Response) => { } }); -// Get housing reviews for a room -router.get("/:room/reviews", async (req: Request, res: Response) => { +/** + * @route GET /api/campus/:room/reviews + * @desc Get housing reviews for a room + * @access Public + */ +router.get("/:room/reviews", isAuthenticated, async (req: Request, res: Response) => { try { // Get room id and convert it to a number const roomId = parseInt(req.params.room, 10); @@ -181,7 +178,12 @@ router.get("/:room/reviews", async (req: Request, res: Response) => { } }); -router.get("/:buildingId/:roomNumber/reviews", async (req: Request, res: Response) => { +/** + * @route GET /api/campus/housing/:buildingId/:roomNumber/reviews + * @desc Get reviews for a room by building id and room number + * @access Public + */ +router.get("/:buildingId/:roomNumber/reviews", isAuthenticated, async (req: Request, res: Response) => { try { // Get room id and convert it to a number const { buildingId, roomNumber} = req.params; @@ -259,7 +261,13 @@ router.get("/:buildingId/:roomNumber/reviews", async (req: Request, res: Respons } }); -router.post("/:buildingId/:roomNumber/reviews", upload.array("pictures"), async (req: Request, res: Response) => { + +/** + * @route POST /api/campus/housing/:buildingId/:roomNumber/reviews + * @desc Add new housing room review + * @access Public + */ +router.post("/:buildingId/:roomNumber/reviews", isAuthenticated, upload.array("pictures"), async (req: Request, res: Response) => { try { const pictureIds: ObjectId[] = []; @@ -342,14 +350,19 @@ router.post("/:buildingId/:roomNumber/reviews", upload.array("pictures"), async } }); -router.patch("/reviews/:id", upload.array("pictures"), async (req: Request, res: Response) => { +/** + * @route PATCH /api/campus/housing/reviews/:id + * @desc Update housing review by review id + * @access isHousingReviewOwner + */ +router.patch("/reviews/:reviewId", isHousingReviewOwner, upload.array("pictures"), async (req: Request, res: Response) => { try { if (!req.files && !req.body) { return; } - const id = req.params.id; - const oldReview = await HousingReviews.findOne({ id: id }); + const reviewId = req.params.reviewId; + const oldReview = await HousingReviews.findOne({ id: reviewId }); if (!oldReview) { console.log("cant find old review") @@ -410,7 +423,7 @@ router.patch("/reviews/:id", upload.array("pictures"), async (req: Request, res: } const updatedReview = await HousingReviews.findOneAndUpdate( - { id: id }, + { id: reviewId }, updateData, { new: true } ); @@ -425,9 +438,14 @@ router.patch("/reviews/:id", upload.array("pictures"), async (req: Request, res: } }); -router.delete("/reviews/:id", async (req: Request, res: Response) => { +/** + * @route DELETE /api/campus/housing/reviews/:id + * @desc Delete housing room review + * @access isHousingReviewOwner + */ +router.delete("/reviews/:reviewId", isHousingReviewOwner, async (req: Request, res: Response) => { try { - const review = await HousingReviews.findOneAndDelete({ id: req.params.id }); + const review = await HousingReviews.findOneAndDelete({ id: req.params.reviewId }); if (!review) { res.status(404).json({ message: "Review not found" }); @@ -439,8 +457,12 @@ router.delete("/reviews/:id", async (req: Request, res: Response) => { } }); -// Get profile picture by id -router.get("/review_pictures/:id", async (req: Request, res: Response) => { +/** + * @route GET /api/campus/housing/review_pictures/:id + * @desc Get review picture by id + * @access Public + */ +router.get("/review_pictures/:id", isAuthenticated, async (req: Request, res: Response) => { try { const fileId = new ObjectId(req.params.id); diff --git a/backend/src/routes/admin/PagesRoutes.ts b/backend/src/routes/admin/PagesRoutes.ts index c31f042..5b89833 100644 --- a/backend/src/routes/admin/PagesRoutes.ts +++ b/backend/src/routes/admin/PagesRoutes.ts @@ -1,5 +1,9 @@ import express, { Request, Response } from "express"; import PageContent from "../../models/PageContent"; +import { + isAdmin, + isAuthenticated, +} from "../../middleware/authMiddleware"; const router = express.Router(); @@ -14,7 +18,7 @@ router.get("/", async (req: Request, res: Response) => { }); // Get the page by id -router.get("/:id", async (req: Request, res: Response) => { +router.get("/:id", isAuthenticated, async (req: Request, res: Response) => { try { const { id } = req.params; const page = await PageContent.findOne({ id }); @@ -42,7 +46,7 @@ router.get("/header/:header", async (req: Request, res: Response) => { }); // Create a new page -router.post("/", async (req: Request, res: Response) => { +router.post("/", isAdmin, async (req: Request, res: Response) => { try { const { id, name, content, header, link } = req.body; @@ -79,7 +83,7 @@ router.post("/", async (req: Request, res: Response) => { }); // Update an existing page -router.put("/:id", async (req: Request, res: Response) => { +router.put("/:id", isAdmin, async (req: Request, res: Response) => { try { const { id } = req.params; const { newId, name, content, header, link } = req.body; @@ -116,7 +120,7 @@ router.put("/:id", async (req: Request, res: Response) => { }); // Delete a page by id -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", isAdmin, async (req: Request, res: Response) => { try { const { id } = req.params; const page = await PageContent.findOneAndDelete({ id }); diff --git a/backend/src/routes/admin/StaffRoutes.ts b/backend/src/routes/admin/StaffRoutes.ts index c6e9266..8743ed0 100644 --- a/backend/src/routes/admin/StaffRoutes.ts +++ b/backend/src/routes/admin/StaffRoutes.ts @@ -3,6 +3,11 @@ import multer from "multer"; import { ObjectId } from "mongodb"; import { Staff } from "../../models/People"; import { bucket } from "../../server"; +import { + isAdmin, + isAuthenticated, +} from "../../middleware/authMiddleware"; + const router = express.Router(); @@ -20,7 +25,7 @@ router.get("/", async (req: Request, res: Response) => { }); // Get staff info by id -router.get("/:id", async (req: Request, res: Response) => { +router.get("/:id", isAuthenticated, async (req: Request, res: Response) => { try { const { id } = req.params; const staff = await Staff.findOne({ id: id }); @@ -36,7 +41,7 @@ router.get("/:id", async (req: Request, res: Response) => { }); // Get staff info by group -router.get("/group/:group", async (req: Request, res: Response) => { +router.get("/group/:group", isAuthenticated, async (req: Request, res: Response) => { try { const { group } = req.params; const staff = await Staff.find({ group: group }); @@ -53,7 +58,7 @@ router.get("/group/:group", async (req: Request, res: Response) => { }); // Create staff member -router.post("/", upload.single("file"), async (req: Request, res: Response) => { +router.post("/", isAdmin, upload.single("file"), async (req: Request, res: Response) => { if (!req.file) { res.status(400).send("No file uploaded"); return; @@ -92,6 +97,7 @@ router.post("/", upload.single("file"), async (req: Request, res: Response) => { // Update staff info router.patch( "/:id", + isAdmin, upload.single("file"), async (req: Request, res: Response) => { try { @@ -191,7 +197,7 @@ router.get("/profile-pic/:id", async (req: Request, res: Response) => { } }); -router.delete("/:id", async (req: Request, res: Response) => { +router.delete("/:id", isAdmin, async (req: Request, res: Response) => { try { const staff = await Staff.findOneAndDelete({ id: req.params.id }); diff --git a/frontend/src/app/campus/courses/[id]/page.tsx b/frontend/src/app/campus/courses/[id]/page.tsx index f1f2509..22a73f8 100644 --- a/frontend/src/app/campus/courses/[id]/page.tsx +++ b/frontend/src/app/campus/courses/[id]/page.tsx @@ -50,96 +50,95 @@ const CoursePage = () => { } }; - useEffect(() => { - const fetchReviews = async () => { - try { - // Function to calculate average ratings - //-------------------------------------- - const calculateAverage = (reviews: CourseReview[]) => { - if (!reviews || reviews.length === 0) { - return { - overallAverage: 0, - inclusivityAverage: 0, - challengeAverage: 0, - workPerWeekAverage: 0, - reviewCount: 0, - }; - } - - let overallSum = 0; - let overallCount = 0; - let inclusivitySum = 0; - let inclusivityCount = 0; - let challengeSum = 0; - let challengeCount = 0; - let workPerWeekSum = 0; - let workPerWeekCount = 0; - - reviews.forEach((review) => { - if (review.overall_rating) { - overallSum += review.overall_rating; - overallCount++; - } - if (review.inclusivity_rating) { - inclusivitySum += review.inclusivity_rating; - inclusivityCount++; - } - if (review.challenge_rating) { - challengeSum += review.challenge_rating; - challengeCount++; - } - if (review.work_per_week) { - workPerWeekSum += review.work_per_week; - workPerWeekCount++; - } - }); - - return { - overallAverage: - overallCount > 0 ? overallSum / overallCount : 0, - inclusivityAverage: - inclusivityCount > 0 - ? inclusivitySum / inclusivityCount - : 0, - challengeAverage: - challengeCount > 0 - ? challengeSum / challengeCount - : 0, - workPerWeekAverage: - workPerWeekCount > 0 - ? workPerWeekSum / workPerWeekCount - : 0, - reviewCount: reviews.length, - }; - }; - //----------------------------------------- - - setLoading(true); - - // Fetch course data - const coursesResponse = await fetch( - `${process.env.BACKEND_LINK}/api/courses/${id}` - ); + useEffect(() => { + const fetchReviews = async () => { + try { + // Function to calculate average ratings + //-------------------------------------- + const calculateAverage = (reviews: CourseReview[]) => { + if (!reviews || reviews.length === 0) { + return { + overallAverage: 0, + inclusivityAverage: 0, + challengeAverage: 0, + workPerWeekAverage: 0, + reviewCount: 0, + }; + } + + let overallSum = 0; + let overallCount = 0; + let inclusivitySum = 0; + let inclusivityCount = 0; + let challengeSum = 0; + let challengeCount = 0; + let workPerWeekSum = 0; + let workPerWeekCount = 0; + + reviews.forEach((review) => { + if (review.overall_rating) { + overallSum += review.overall_rating; + overallCount++; + } + if (review.inclusivity_rating) { + inclusivitySum += review.inclusivity_rating; + inclusivityCount++; + } + if (review.challenge_rating) { + challengeSum += review.challenge_rating; + challengeCount++; + } + if (review.work_per_week) { + workPerWeekSum += review.work_per_week; + workPerWeekCount++; + } + }); + + return { + overallAverage: overallCount > 0 ? overallSum / overallCount : 0, + inclusivityAverage: + inclusivityCount > 0 ? inclusivitySum / inclusivityCount : 0, + challengeAverage: + challengeCount > 0 ? challengeSum / challengeCount : 0, + workPerWeekAverage: + workPerWeekCount > 0 ? workPerWeekSum / workPerWeekCount : 0, + reviewCount: reviews.length, + }; + }; + //----------------------------------------- - if (!coursesResponse.ok) { - throw new Error( - `Failed to fetch courses: ${coursesResponse.status}` - ); - } + setLoading(true); - const courseData: Course = await coursesResponse.json(); - setCourseName(courseData.name); + // Fetch course data + const coursesResponse = await fetch( + `${process.env.BACKEND_LINK}/api/courses/${id}`, + { + method: "GET", + credentials: "include", + } + ); - // Fetch instructors - const instructorResponse = await fetch( - `${process.env.BACKEND_LINK}/api/courses/${courseData.id}/instructors` - ); + if (!coursesResponse.ok) { + throw new Error(`Failed to fetch courses: ${coursesResponse.status}`); + } - if (!instructorResponse.ok) { - throw new Error( - `Failed to fetch instructors: ${coursesResponse.status}` - ); - } + const courseData: Course = await coursesResponse.json(); + setCourseName(courseData.name); + + // Fetch instructors + const instructorResponse = await fetch( + `${process.env.BACKEND_LINK}/api/courses/${courseData.id}/instructors`, + { + method: "GET", + credentials: "include", // This allows sending cookies for session identification + } + ); + + if (!instructorResponse.ok) { + throw new Error( + `Failed to fetch instructors: ${coursesResponse.status}` + ); + } const instructorData: Instructor[] = await instructorResponse.json(); @@ -150,16 +149,18 @@ const CoursePage = () => { ); setInstructors(filteredInstructors); - // Fetch course reviews - const reviews = await fetch( - `${process.env.BACKEND_LINK}/api/courses/${id}/reviews` - ); - - if (!reviews.ok) { - throw new Error( - `Failed to fetch course reviews: ${reviews.status}` - ); - } + // Fetch course reviews + const reviews = await fetch( + `${process.env.BACKEND_LINK}/api/courses/${id}/reviews`, + { + method: "GET", + credentials: "include", // This allows sending cookies for session identification + } + ); + + if (!reviews.ok) { + throw new Error(`Failed to fetch course reviews: ${reviews.status}`); + } const reviewsData: CourseReview[] = await reviews.json(); @@ -204,44 +205,44 @@ const CoursePage = () => { setInstructorMap(instructorMapping); - setAverageRatings(calculateAverage(reviewsData)); - - const coursesWithReviews: CourseWithReviews = { - reviews: reviewsData, - course: courseData, - }; + setAverageRatings(calculateAverage(reviewsData)); - setCourseReviews(coursesWithReviews); - } catch (error) { - console.error("Server error", error); - } finally { - setLoading(false); - } + const coursesWithReviews: CourseWithReviews = { + reviews: reviewsData, + course: courseData, }; - fetchReviews(); - }, [id]); + setCourseReviews(coursesWithReviews); + } catch (error) { + console.error("Server error", error); + } finally { + setLoading(false); + } + }; - const targetRef = useRef(null); + fetchReviews(); + }, [id]); - const scrollToReviewForm = () => { - setTimeout(() => { - if (targetRef.current) { - targetRef.current.scrollIntoView({ - behavior: "smooth", - block: "start", - }); - } - }, 0); - }; + const targetRef = useRef(null); - if (loading || authLoading) { - return ; - } + const scrollToReviewForm = () => { + setTimeout(() => { + if (targetRef.current) { + targetRef.current.scrollIntoView({ + behavior: "smooth", + block: "start", + }); + } + }, 0); + }; - if (!user) { - return ; - } + if (loading || authLoading) { + return ; + } + + if (!user) { + return ; + } const formatDate = (date: Date) => { const d = new Date(date); @@ -250,54 +251,53 @@ const CoursePage = () => { return `${month} ${year}`; }; - // Format average work per week to a readable string - const formatWorkPerWeek = (hours: number) => { - if (hours === 0) return "N/A"; - if (hours < 1) return "Less than 1 hour"; - if (hours < 2) return "1 hour"; - return `${Math.round(hours)} hours`; - }; + // Format average work per week to a readable string + const formatWorkPerWeek = (hours: number) => { + if (hours === 0) return "N/A"; + if (hours < 1) return "Less than 1 hour"; + if (hours < 2) return "1 hour"; + return `${Math.round(hours)} hours`; + }; + + const handleDelete = async (id: number) => { + if (window.confirm("Are you sure you want to delete this review?")) { + try { + setLoading(true); + const response = await fetch( + `${process.env.BACKEND_LINK}/api/courses/reviews/${id}`, + { + method: "DELETE", + credentials: "include", + } + ); + + if (!response.ok) { + throw new Error("Failed to delete review"); + } - const handleDelete = async (id: number) => { - if (window.confirm("Are you sure you want to delete this review?")) { - try { - setLoading(true); - const response = await fetch( - `${process.env.BACKEND_LINK}/api/courses/reviews/${id}`, - { - method: "DELETE", - } - ); + alert("Review deleted successfully!"); + setTimeout(() => window.location.reload(), 1000); + } catch (error) { + console.error("Error deleting review", error); + alert("Failed to delete review"); + } finally { + setLoading(false); + } + } + }; - if (!response.ok) { - throw new Error("Failed to delete review"); - } - - alert("Review deleted successfully!"); - setTimeout(() => window.location.reload(), 1000); - } catch (error) { - console.error("Error deleting review", error); - alert("Failed to delete review"); - } finally { - setLoading(false); - } - } - }; + return ( +
+ {/* Back Button */} + - return ( -
- {/* Back Button */} - - -
-

- {courseName} -

+
+

{courseName}

{courseReviews ? ( @@ -330,113 +330,95 @@ const CoursePage = () => {
)} - {courseReviews.course.department_names && - courseReviews.course.department_names - .length > 0 && ( -
-

- Departments: -

-

- {courseReviews.course.department_names - .map((dept) => - dept.includes( - "Philosophy,Politics,Econ" - ) - ? "PPE" - : dept - ) - .join(", ")} -

-
- )} - - {courseReviews.course.requirement_names && - courseReviews.course.requirement_names - .length > 0 && ( -
-

- Requirements Fulfilled: -

-

- {courseReviews.course.requirement_names.join( - ", " - )} -

-
- )} + {courseReviews.course.department_names && + courseReviews.course.department_names.length > 0 && ( +
+

+ Departments: +

+

+ {courseReviews.course.department_names + .map((dept) => + dept.includes("Philosophy,Politics,Econ") + ? "PPE" + : dept + ) + .join(", ")} +

+
+ )} - {courseReviews.course.term_keys && - courseReviews.course.term_keys.length > - 0 ? ( -
-

- Terms Offered (After 2020): -

-

- {courseReviews.course.term_keys - .filter((term) => { - // Extract the year from term (format: "2002;FA") - const year = parseInt( - term.split(";")[0], - 10 - ); - // Only include terms after 2020 - return year > 2020; - }) - .map((term) => { - const [year, semester] = - term.split(";"); - - // Convert semester code to full name - let semesterName = ""; - switch (semester) { - case "FA": - semesterName = - "Fall"; - break; - case "SP": - semesterName = - "Spring"; - break; - case "SU": - semesterName = - "Summer"; - break; - case "WI": - semesterName = - "Winter"; - break; - default: - semesterName = - semester; - } - - return `${semesterName} ${year}`; - }) - .join(", ")} -

-
- ) : ( -
-

- Terms Offered: -

-

Offered most terms

-
- )} -
+ {courseReviews.course.requirement_names && + courseReviews.course.requirement_names.length > 0 && ( +
+

+ Requirements Fulfilled: +

+

+ {courseReviews.course.requirement_names.join(", ")} +

+
+ )} - {courseReviews.course.description && ( -
-

- Description: -

-

- {courseReviews.course.description} -

-
- )} + {courseReviews.course.term_keys && + courseReviews.course.term_keys.length > 0 ? ( +
+

+ Terms Offered (After 2020): +

+

+ {courseReviews.course.term_keys + .filter((term) => { + // Extract the year from term (format: "2002;FA") + const year = parseInt(term.split(";")[0], 10); + // Only include terms after 2020 + return year > 2020; + }) + .map((term) => { + const [year, semester] = term.split(";"); + + // Convert semester code to full name + let semesterName = ""; + switch (semester) { + case "FA": + semesterName = "Fall"; + break; + case "SP": + semesterName = "Spring"; + break; + case "SU": + semesterName = "Summer"; + break; + case "WI": + semesterName = "Winter"; + break; + default: + semesterName = semester; + } + + return `${semesterName} ${year}`; + }) + .join(", ")} +

+
+ ) : ( +
+

+ Terms Offered: +

+

Offered most terms

+
+ )} +
+ + {courseReviews.course.description && ( +
+

Description:

+

+ {courseReviews.course.description} +

+
+ )} {averageRatings && averageRatings.reviewCount > 0 && ( @@ -528,70 +510,61 @@ const CoursePage = () => { Add New Review -
-
-
- - ) : null} - - {/* User Reviews */} - {courseReviews ? ( -
-

- Student Reviews -

- {courseReviews.reviews.length > 0 ? ( - courseReviews.reviews.map((review) => ( -
-
-
- - Overall Rating: - - - - - - {review.overall_rating || - ""} - -
- - {user.email == - review.user_email && ( -
- - -
- )} -
+
+
+
+ + ) : null} + + {/* User Reviews */} + {courseReviews ? ( +
+

+ Student Reviews +

+ {courseReviews.reviews.length > 0 ? ( + courseReviews.reviews.map((review) => ( +
+
+
+ + Overall Rating: + + + + + + {review.overall_rating || ""} + +
+ + {user.email == review.user_email && ( +
+ + +
+ )} +
{/* Display instructor name */} {review.instructor_id && @@ -656,63 +629,53 @@ const CoursePage = () => {
- {review.comments && ( -
-

- {review.comments} -

-
- )} - - {/* Date written, last updated */} -
-

- Review written{" "} - {review.createdAt && - formatDate( - review.createdAt - )} -

-

- Last updated{" "} - {review.updatedAt && - formatDate( - review.updatedAt - )} -

-
-
- )) - ) : ( -
-

- No reviews yet for this course. -

-

- Be the first to leave a review! -

-
- )} -
- ) : ( -
-

- No reviews yet for this course. -

-

- Be the first to leave a review! -

-
+ {review.comments && ( +
+

{review.comments}

+
)} + + {/* Date written, last updated */} +
+

+ Review written{" "} + {review.createdAt && formatDate(review.createdAt)} +

+

+ Last updated{" "} + {review.updatedAt && formatDate(review.updatedAt)} +

+
+
+ )) + ) : ( +
+

+ No reviews yet for this course. +

+

+ Be the first to leave a review! +

+ )} +
+ ) : ( +
+

+ No reviews yet for this course. +

+

Be the first to leave a review!

+
+ )} + - + {(isCreatingNew || selectedReview) && (
diff --git a/frontend/src/app/campus/courses/page.tsx b/frontend/src/app/campus/courses/page.tsx index 810f83d..7ac07f2 100644 --- a/frontend/src/app/campus/courses/page.tsx +++ b/frontend/src/app/campus/courses/page.tsx @@ -67,7 +67,8 @@ const CourseSearchComponent = () => { const responses = await Promise.all( uncachedIds.map(id => axios.get(`${process.env.BACKEND_LINK}/api/instructors/${id}`, { - timeout: 3000 + timeout: 3000, + withCredentials: true }).catch(() => null) ) ); @@ -114,7 +115,8 @@ const CourseSearchComponent = () => { limit: 100 }, timeout: 5000, - cancelToken: source.token + cancelToken: source.token, + withCredentials: true }); setResults(response.data); diff --git a/frontend/src/app/campus/housing/[id]/[room]/page.tsx b/frontend/src/app/campus/housing/[id]/[room]/page.tsx index 58e8c80..113b95c 100644 --- a/frontend/src/app/campus/housing/[id]/[room]/page.tsx +++ b/frontend/src/app/campus/housing/[id]/[room]/page.tsx @@ -44,7 +44,10 @@ const RoomPage = () => { // Fetch building data const buildingResponse = await fetch( - `${process.env.BACKEND_LINK}/api/campus/housing/${id}` + `${process.env.BACKEND_LINK}/api/campus/housing/${id}`, + { + credentials: "include", + } ); if (!buildingResponse.ok) { @@ -58,7 +61,10 @@ const RoomPage = () => { // Fetch reviews const reviews = await fetch( - `${process.env.BACKEND_LINK}/api/campus/housing/${id}/${room}/reviews` + `${process.env.BACKEND_LINK}/api/campus/housing/${id}/${room}/reviews`, + { + credentials: "include", + } ); if (!reviews.ok) { @@ -85,10 +91,10 @@ const RoomPage = () => { if (targetRef.current) { targetRef.current.scrollIntoView({ behavior: "smooth", - block: "start", + block: "start", }); } - }, 0); + }, 0); }; if (loading || authLoading) { @@ -114,6 +120,7 @@ const RoomPage = () => { `${process.env.BACKEND_LINK}/api/campus/housing/reviews/${id}`, { method: "DELETE", + credentials: "include", } ); @@ -136,7 +143,7 @@ const RoomPage = () => {
{/* Back Button */}
-
+
Quiet: diff --git a/frontend/src/app/campus/housing/[id]/page.tsx b/frontend/src/app/campus/housing/[id]/page.tsx index c6b21c5..ddb9fc3 100644 --- a/frontend/src/app/campus/housing/[id]/page.tsx +++ b/frontend/src/app/campus/housing/[id]/page.tsx @@ -27,7 +27,10 @@ export default function DynamicRooms() { roomsList.map(async (room) => { try { const response = await fetch( - `${process.env.BACKEND_LINK}/api/campus/housing/${room.id}/reviews` + `${process.env.BACKEND_LINK}/api/campus/housing/${room.id}/reviews`, + { + credentials: "include", + } ); if (response.ok) { @@ -58,7 +61,6 @@ export default function DynamicRooms() { // Validate building parameter is a number const buildingId = Number(id); - console.log(buildingId); if (isNaN(buildingId)) { setBuildingNotFound(true); @@ -68,9 +70,11 @@ export default function DynamicRooms() { // First fetch building info to check if it exists const buildingResponse = await fetch( - `${process.env.BACKEND_LINK}/api/campus/housing/${buildingId}` + `${process.env.BACKEND_LINK}/api/campus/housing/${buildingId}`, + { + credentials: "include", + } ); - console.log(buildingResponse); if (!buildingResponse.ok) { if (buildingResponse.status === 404) { @@ -95,7 +99,10 @@ export default function DynamicRooms() { // Now fetch rooms for this building const response = await fetch( - `${process.env.BACKEND_LINK}/api/campus/housing/${buildingId}/rooms` + `${process.env.BACKEND_LINK}/api/campus/housing/${buildingId}/rooms`, + { + credentials: "include", + } ); if (!response.ok) { @@ -155,12 +162,12 @@ export default function DynamicRooms() {
{/* Back Button */} - +

{building.name}

- {Array.from({ length: building.floors }).map((_, i) => { - const isLastInOddSet = building.floors % 2 !== 0 && i === building.floors - 1; + {Array.from({ length: building.floors }).map((_, i) => { + const isLastInOddSet = + building.floors % 2 !== 0 && i === building.floors - 1; const isOnlyOne = building.floors === 1; const shouldSpanAndCenter = isLastInOddSet || isOnlyOne; return (
{`Floor { e.currentTarget.src = '/placeholder-floorplan.jpg'; }} + className={`w-full h-auto rounded-lg border border-gray-200 shadow ${ + shouldSpanAndCenter ? "sm:max-w-2xl" : "" + }`} + onError={(e) => { + e.currentTarget.src = "/placeholder-floorplan.jpg"; + }} />
); diff --git a/frontend/src/app/campus/instructors/[id]/page.tsx b/frontend/src/app/campus/instructors/[id]/page.tsx index fda3795..3ecb7c0 100644 --- a/frontend/src/app/campus/instructors/[id]/page.tsx +++ b/frontend/src/app/campus/instructors/[id]/page.tsx @@ -140,7 +140,10 @@ const InstructorPage = (): JSX.Element => { // Fetch instructor data const instructorResponse = await fetch( - `${process.env.BACKEND_LINK}/api/instructors/${instructorId}` + `${process.env.BACKEND_LINK}/api/instructors/${instructorId}`, + { + credentials: "include", + } ); if (!instructorResponse.ok) { @@ -157,7 +160,10 @@ const InstructorPage = (): JSX.Element => { // Fetch instructor reviews const reviews = await fetch( - `${process.env.BACKEND_LINK}/api/instructors/${instructorId}/reviews` + `${process.env.BACKEND_LINK}/api/instructors/${instructorId}/reviews`, + { + credentials: "include", + } ); if (!reviews.ok) { @@ -232,6 +238,7 @@ const InstructorPage = (): JSX.Element => { `${process.env.BACKEND_LINK}/api/courses/reviews/${id}`, // Reusing the course review endpoint { method: "DELETE", + credentials:"include", } ); diff --git a/frontend/src/app/pages/[header]/[pageid]/page.tsx b/frontend/src/app/pages/[header]/[pageid]/page.tsx index 503bb34..48cb9cc 100644 --- a/frontend/src/app/pages/[header]/[pageid]/page.tsx +++ b/frontend/src/app/pages/[header]/[pageid]/page.tsx @@ -6,86 +6,83 @@ import Loading from "@/components/Loading"; import { PageContent } from "@/types"; export default function DynamicPage() { - const params = useParams(); - const { header, pageid } = params; + const params = useParams(); + const { header, pageid } = params; - const [pageData, setPageData] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const [pageData, setPageData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - const fetchPageData = async () => { - try { - setLoading(true); - const response = await fetch( - `${process.env.BACKEND_LINK}/api/admin/pages/${pageid}` - ); - - if (!response.ok) { - console.log(pageid); - throw new Error(`Failed to fetch page: ${response.status}`); - } - - const data = await response.json(); - setPageData(data); - } catch (error) { - console.error("Error fetching page data:", error); - setError( - "Failed to load page content. Please try again later." - ); - } finally { - setLoading(false); - } - }; + useEffect(() => { + const fetchPageData = async () => { + try { + setLoading(true); + const response = await fetch( + `${process.env.BACKEND_LINK}/api/admin/pages/${pageid}`, + { + credentials: "include", + } + ); - if (pageid) { - fetchPageData(); + if (!response.ok) { + throw new Error(`Failed to fetch page: ${response.status}`); } - }, [pageid]); - if (loading) { - return ; - } + const data = await response.json(); + setPageData(data); + } catch (error) { + console.error("Error fetching page data:", error); + setError("Failed to load page content. Please try again later."); + } finally { + setLoading(false); + } + }; - if (error) { - return ( -
-
-

{error}

-
-
- ); + if (pageid) { + fetchPageData(); } + }, [pageid]); - if (!pageData) { - return ( -
-

Page Not Found

-

The requested page could not be found.

-
- ); - } + if (loading) { + return ; + } - if (!pageData.content) { - return ( -
-

{pageData.name}

-

- This page is a placeholder. Please check back later for - updates. -

-
- ); - } + if (error) { + return ( +
+
+

{error}

+
+
+ ); + } + if (!pageData) { return ( -
-

{pageData.name}

+
+

Page Not Found

+

The requested page could not be found.

+
+ ); + } -
-
+ if (!pageData.content) { + return ( +
+

{pageData.name}

+

This page is a placeholder. Please check back later for updates.

+
); + } + + return ( +
+

{pageData.name}

+ +
+
+ ); } diff --git a/frontend/src/app/staff/[slug]/page.tsx b/frontend/src/app/staff/[slug]/page.tsx index f164666..9772a02 100644 --- a/frontend/src/app/staff/[slug]/page.tsx +++ b/frontend/src/app/staff/[slug]/page.tsx @@ -6,126 +6,131 @@ import { StaffMember, PageProps } from "@/types"; import Image from "next/image"; const groupSlugMap: { [key: string]: string } = { - "Senate": "senate", - "Staff": "staff", - "CollegeStaff": "college-staff", - "Software": "software", + Senate: "senate", + Staff: "staff", + CollegeStaff: "college-staff", + Software: "software", }; const SenatePage: React.FC = ({ params }) => { - const resolvedParams = React.use(params); - console.log(resolvedParams.slug); - const { loading } = useAuth(); - const [members, setMembers] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [showBio, setShowBio] = useState(null); + const resolvedParams = React.use(params); + const { loading } = useAuth(); + const [members, setMembers] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [showBio, setShowBio] = useState(null); - useEffect(() => { - const fetchData = async () => { - try { - setIsLoading(true); + useEffect(() => { + const fetchData = async () => { + try { + setIsLoading(true); - // Fetch members by group - const res = await fetch( - `${process.env.BACKEND_LINK}/api/members/group/${ - groupSlugMap[resolvedParams.slug] - }` - ); + // Fetch members by group + const res = await fetch( + `${process.env.BACKEND_LINK}/api/members/group/${ + groupSlugMap[resolvedParams.slug] + }`, + { + credentials: "include", + } + ); - if (!res.ok) { - throw new Error("Failed to fetch members"); - } + if (!res.ok) { + throw new Error("Failed to fetch members"); + } - const data = await res.json(); - setMembers(data); - } catch (error) { - console.error("Error fetching data:", error); - } finally { - setIsLoading(false); - } - }; + const data = await res.json(); + setMembers(data); + } catch (error) { + console.error("Error fetching data:", error); + } finally { + setIsLoading(false); + } + }; - if (resolvedParams.slug) { - fetchData(); - } - }, [resolvedParams.slug]); + if (resolvedParams.slug) { + fetchData(); + } + }, [resolvedParams.slug]); - if (loading || isLoading) return ; + if (loading || isLoading) return ; - const toggleBio = (name: string) => { - if (showBio === name) { - setShowBio(null); - } else { - setShowBio(name); - } - }; - + const toggleBio = (name: string) => { + if (showBio === name) { + setShowBio(null); + } else { + setShowBio(name); + } + }; - return ( -
-
-

- ASPC {resolvedParams.slug.replace(/([a-z])([A-Z])/g, '$1 $2')} -

+ return ( +
+
+

+ ASPC {resolvedParams.slug.replace(/([a-z])([A-Z])/g, "$1 $2")} +

- {/* Senator Cards */} + {/* Senator Cards */} - {members.length === 0 ? ( -
- No members found in this group. -
- ) : ( -
- {members.map((member) => ( -
-
-
-
- {showBio === member.name ? ( -
-

{member.bio}

-
- ) : ( - {member.name} { - const target = - e.target as HTMLImageElement; - target.src = "/cecil.jpg"; // Direct src replacement - }} - layout="fill" - /> - )} -
-

- {member.name} -

-

- {member.position} -

- -
-
-
- ))} + {members.length === 0 ? ( +
+ No members found in this group. +
+ ) : ( +
+ {members.map((member) => ( +
+
+
+
+ {showBio === member.name ? ( +
+

{member.bio}

+
+ ) : ( + {member.name} { + const target = e.target as HTMLImageElement; + target.src = "/cecil.jpg"; // Direct src replacement + }} + layout="fill" + /> + )}
- )} -
-
- ); +

+ {member.name} +

+

{member.position}

+ +
+
+
+ ))} +
+ )} +
+
+ ); }; export default SenatePage; diff --git a/frontend/src/components/courses/Review.tsx b/frontend/src/components/courses/Review.tsx index 92b78fd..0ff4b43 100644 --- a/frontend/src/components/courses/Review.tsx +++ b/frontend/src/components/courses/Review.tsx @@ -104,7 +104,10 @@ export const ReviewForm: React.FC = ({ if (isFromInstructor && instructorId !== undefined) { // Fetch instructor's courses const response = await fetch( - `${process.env.BACKEND_LINK}/api/instructors/${instructorId}/courses` + `${process.env.BACKEND_LINK}/api/instructors/${instructorId}/courses`, + { + credentials: "include", + } ); if (!response.ok) { @@ -116,7 +119,10 @@ export const ReviewForm: React.FC = ({ } else if (courseId !== undefined) { // Fetch course instructors const response = await fetch( - `${process.env.BACKEND_LINK}/api/courses/${courseId}/instructors` + `${process.env.BACKEND_LINK}/api/courses/${courseId}/instructors`, + { + credentials: "include", + } ); if (!response.ok) { @@ -220,6 +226,7 @@ export const ReviewForm: React.FC = ({ "Content-Type": "application/json", }, body: JSON.stringify(reviewPayload), + credentials: "include", }); if (!response.ok) { diff --git a/frontend/src/components/housing/Reviews.tsx b/frontend/src/components/housing/Reviews.tsx index fcb52c1..0924b00 100644 --- a/frontend/src/components/housing/Reviews.tsx +++ b/frontend/src/components/housing/Reviews.tsx @@ -3,9 +3,11 @@ import React from "react"; import { useParams } from "next/navigation"; import { useEffect, useState } from "react"; import { ReviewFormProps } from "@/types"; +import { useAuth } from "@/hooks/useAuth"; import Image from "next/image"; export const ReviewForm: React.FC = ({ review }) => { + const { user } = useAuth(); const params = useParams(); const { id, room } = params; @@ -113,20 +115,10 @@ export const ReviewForm: React.FC = ({ review }) => { } try { - // Get current user's email - const userResponse = await fetch( - `${process.env.BACKEND_LINK}/api/auth/current_user`, - { - credentials: "include", - } - ); - - if (!userResponse.ok) { + if (!user) { throw new Error("Error getting current user"); } - const user = await userResponse.json(); - // Construct review request const formData = new FormData(); formData.append("overall", ratings.overall.toString()); @@ -134,7 +126,7 @@ export const ReviewForm: React.FC = ({ review }) => { formData.append("layout", ratings.layout.toString()); formData.append("temperature", ratings.temperature.toString()); formData.append("comments", comments); - formData.append("email", user.user.email); + formData.append("email", user.email); if (pictures) { Array.from(pictures).forEach((file) => { @@ -151,6 +143,7 @@ export const ReviewForm: React.FC = ({ review }) => { const response = await fetch(url, { method, body: formData, + credentials: "include", }); if (!response.ok) { diff --git a/frontend/src/components/ui/PageDashboard.tsx b/frontend/src/components/ui/PageDashboard.tsx index 84ce699..76271af 100644 --- a/frontend/src/components/ui/PageDashboard.tsx +++ b/frontend/src/components/ui/PageDashboard.tsx @@ -106,7 +106,11 @@ const PageDashboard = () => { try { setLoading(true); const response = await fetch( - `${process.env.BACKEND_LINK}/api/admin/pages/${pageId}` + `${process.env.BACKEND_LINK}/api/admin/pages/${pageId}`, + { + method: "GET", + credentials: "include", + } ); if (response.ok) { @@ -140,7 +144,11 @@ const PageDashboard = () => { try { setLoading(true); const response = await fetch( - `${process.env.BACKEND_LINK}/api/admin/pages/${pageId}` + `${process.env.BACKEND_LINK}/api/admin/pages/${pageId}`, + { + method: "GET", + credentials: "include", + } ); if (response.ok) { @@ -182,6 +190,7 @@ const PageDashboard = () => { `${process.env.BACKEND_LINK}/api/admin/pages`, { method: "POST", + credentials: "include", headers: { "Content-Type": "application/json", }, @@ -217,6 +226,7 @@ const PageDashboard = () => { `${process.env.BACKEND_LINK}/api/admin/pages/${pageIdToUpdate}`, { method: "PUT", + credentials: "include", headers: { "Content-Type": "application/json", }, @@ -276,6 +286,7 @@ const PageDashboard = () => { `${process.env.BACKEND_LINK}/api/admin/pages/${pageIdToDelete}`, { method: "DELETE", + credentials: "include", } ); diff --git a/frontend/src/components/ui/StaffDashboard.tsx b/frontend/src/components/ui/StaffDashboard.tsx index 015a705..1429ad0 100644 --- a/frontend/src/components/ui/StaffDashboard.tsx +++ b/frontend/src/components/ui/StaffDashboard.tsx @@ -25,7 +25,10 @@ const StaffDashboard = () => { const fetchMembers = async () => { try { const response = await fetch( - `${process.env.BACKEND_LINK}/api/members` + `${process.env.BACKEND_LINK}/api/members`, + { + credentials: "include", + } ); if (response.ok) { const data = await response.json(); @@ -49,7 +52,10 @@ const StaffDashboard = () => { try { setIsLoading(true); const response = await fetch( - `${process.env.BACKEND_LINK}/api/members/${selectedMemberId}` + `${process.env.BACKEND_LINK}/api/members/${selectedMemberId}`, + { + credentials: "include", + } ); if (response.ok) { @@ -104,6 +110,7 @@ const StaffDashboard = () => { setIsLoading(true); const response = await fetch(`${process.env.BACKEND_LINK}/api/members/${selectedMemberId}`, { method: "DELETE", + credentials: "include", }); if (!response.ok) { @@ -146,6 +153,7 @@ const StaffDashboard = () => { const response = await fetch(url, { method, + credentials: "include", body: formData, });