From 8e5e16d5185ba41f379cd0b7a7a0d61e8e9bfae9 Mon Sep 17 00:00:00 2001 From: Chenny Date: Wed, 14 May 2025 10:45:19 +0200 Subject: [PATCH 1/2] Basic comment function to each reviews --- my-app/firebase.js | 31 +++++ my-app/firebase_rules.json | 78 ++++++----- my-app/src/model.js | 13 +- my-app/src/views/Components/CommentTree.jsx | 83 +++++++++++ my-app/src/views/ReviewView.jsx | 146 ++++++++++---------- 5 files changed, 240 insertions(+), 111 deletions(-) create mode 100644 my-app/src/views/Components/CommentTree.jsx diff --git a/my-app/firebase.js b/my-app/firebase.js index 7153d34..85e7ca4 100644 --- a/my-app/firebase.js +++ b/my-app/firebase.js @@ -3,6 +3,7 @@ import { getAuth, GoogleAuthProvider, onAuthStateChanged } from "firebase/auth"; import { getFunctions, httpsCallable } from 'firebase/functions'; import { get, getDatabase, ref, set, onValue, onChildRemoved, onChildAdded, runTransaction } from "firebase/database"; import { reaction, toJS } from "mobx"; +import { push } from "firebase/database"; // Your web app's Firebase configuration const firebaseConfig = { @@ -368,3 +369,33 @@ export async function getReviewsForCourse(courseCode) { }); return reviews; } +/** + * Add a comment to a specific review + * @param {string} courseCode - Course code (e.g. "A11HIB") + * @param {string} reviewUserId - The user ID of the person who wrote the main review + * @param {Object} commentObj - Object with { userName, text, timestamp } + */ +export async function addCommentToReview(courseCode, reviewUserId, commentObj) { + const commentsRef = ref(db, `reviews/${courseCode}/${reviewUserId}/comments`); + await push(commentsRef, commentObj); +} + +/** + * Get comments for a specific review + * @param {string} courseCode + * @param {string} reviewUserId + * @returns {Promise>} Array of comments: { id, userName, text, timestamp } + */ +export async function getCommentsForReview(courseCode, reviewUserId) { + const commentsRef = ref(db, `reviews/${courseCode}/${reviewUserId}/comments`); + const snapshot = await get(commentsRef); + if (!snapshot.exists()) return []; + const comments = []; + snapshot.forEach((childSnapshot) => { + comments.push({ + id: childSnapshot.key, + ...childSnapshot.val() + }); + }); + return comments; +} \ No newline at end of file diff --git a/my-app/firebase_rules.json b/my-app/firebase_rules.json index 4af3777..10fc332 100644 --- a/my-app/firebase_rules.json +++ b/my-app/firebase_rules.json @@ -1,42 +1,52 @@ { - "rules": { - // Courses and Metadata - "courses": { - ".read": true, - ".write": "auth != null && (auth.uid === 'adminuid' || auth.uid === 'adminuid')" - }, - "metadata": { - ".read": true, - ".write": "auth != null && (auth.uid === 'adminuid' || auth.uid === 'adminuid')" - }, - "departments": { + "rules": { + // Courses and Metadata + "courses": { ".read": true, - ".write": "auth != null && (auth.uid === 'adminuid' || auth.uid === 'adminuid')" - }, - "locations": { + ".write": "auth != null && auth.uid === 'adminuid'" + }, + "metadata": { + ".read": true, + ".write": "auth != null && auth.uid === 'adminuid'" + }, + "departments": { + ".read": true, + ".write": "auth != null && auth.uid === 'adminuid'" + }, + "locations": { + ".read": true, + ".write": "auth != null && auth.uid === 'adminuid'" + }, + + // Reviews and Comments + "reviews": { ".read": true, - ".write": "auth != null && (auth.uid === 'adminuid' || auth.uid === 'adminuid')" - }, - - // Reviews - "reviews": { - ".read":true, "$courseCode": { - "$userID": { - ".write": "auth != null && (auth.uid === $userID || data.child('uid').val() === auth.uid || !data.exists())", - ".validate": "newData.hasChildren(['text', 'timestamp']) && - newData.child('text').isString() && - newData.child('timestamp').isNumber()" - } - } - }, - - // Users - "users": { "$userID": { - ".read": "auth != null && auth.uid === $userID", - ".write": "auth != null && auth.uid === $userID" + // Only the review owner can write the main review fields (not including comments) + ".write": "auth != null && (auth.uid === $userID)", + + // Allow anyone to write a comment + "comments": { + ".read": true, + "$commentId": { + ".write": "auth != null", + ".validate": "newData.hasChildren(['userName', 'text', 'timestamp']) && + newData.child('userName').isString() && + newData.child('text').isString() && + newData.child('timestamp').isNumber()" + } + } } } + }, + + // Users + "users": { + "$userID": { + ".read": "auth != null && auth.uid === $userID", + ".write": "auth != null && auth.uid === $userID" + } } - } \ No newline at end of file + } +} diff --git a/my-app/src/model.js b/my-app/src/model.js index 2d3996b..af0c1a9 100644 --- a/my-app/src/model.js +++ b/my-app/src/model.js @@ -190,7 +190,18 @@ export const model = { async getReviews(courseCode) { try { - return await getReviewsForCourse(courseCode); + const rawReviews = await getReviewsForCourse(courseCode); + if (!Array.isArray(rawReviews)) return []; + + const enriched = rawReviews.map((review) => { + return { + ...review, + uid: review.uid || review.id || "", + courseCode: courseCode || "", + }; + }); + + return enriched; } catch (error) { console.error("Error fetching reviews:", error); return []; diff --git a/my-app/src/views/Components/CommentTree.jsx b/my-app/src/views/Components/CommentTree.jsx new file mode 100644 index 0000000..af4fcd0 --- /dev/null +++ b/my-app/src/views/Components/CommentTree.jsx @@ -0,0 +1,83 @@ +import React, { useState } from "react"; +import RatingComponent from "./RatingComponent.jsx"; +import { model } from "../../model.js"; // Adjust the path if needed +import { addReviewForCourse } from "../../firebase"; // Adjust the path if needed + +function CommentTree({ courseCode, comment, level = 0 }) { + const [showReply, setShowReply] = useState(false); + const [replyText, setReplyText] = useState(""); + + const handleReplySubmit = async () => { + if (replyText.trim().length === 0) return; + + const reply = { + userName: model.user?.displayName || "Anonymous", + text: replyText, + timestamp: Date.now(), + overallRating: 0, + difficultyRating: 0, + professorRating: 0, + professorName: "", + grade: "", + recommend: null, + }; + + await addReviewForCourse(courseCode, reply, comment.id); + window.location.reload(); // quick reload for now; optional optimization later + }; + + return ( +
+
+
+

{comment.userName}

+

+ {new Date(comment.timestamp).toLocaleDateString()} +

+
+ +

{comment.text}

+ + + + {showReply && ( +
+