diff --git a/api/src/controllers/reviews.ts b/api/src/controllers/reviews.ts index 17e30641..2e9cd840 100644 --- a/api/src/controllers/reviews.ts +++ b/api/src/controllers/reviews.ts @@ -293,5 +293,28 @@ router.delete('/clear', async function (req, res) { res.json({ error: 'Can only clear on development environment' }); } }); +/** + * Updating the review + */ +router.patch('/updateReview', async function (req, res) { + if (req.session.passport) { + const updatedReviewBody = req.body; + + const query = { + _id: new ObjectId(req.body._id), + }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { _id, ...updateWithoutId } = updatedReviewBody; + await updateDocument(COLLECTION_NAMES.REVIEWS, query, { $set: updateWithoutId }); + const responseWithId = { + _id: query._id, + ...updateWithoutId, + }; + + res.json(responseWithId); + } else { + res.status(401).json({ error: 'Must be logged in to update a review.' }); + } +}); export default router; diff --git a/site/src/component/Review/Review.scss b/site/src/component/Review/Review.scss index 16ff812c..1ada20b7 100644 --- a/site/src/component/Review/Review.scss +++ b/site/src/component/Review/Review.scss @@ -212,3 +212,20 @@ } } } + +/* Edit review icon */ +.edit-pen-icon { + float: right; + font-size: 1.5rem; + width: 3rem; + text-align: center; + padding: 0.1rem; + border-radius: 55%; +} + +.edit-pen-icon:hover { + transform: scale(1.1); + transition: transform 0.3s ease; + background: var(--peterportal-mid-gray); + color: white; +} diff --git a/site/src/component/Review/SubReview.tsx b/site/src/component/Review/SubReview.tsx index 4bcb9e6a..5b4aa8e1 100644 --- a/site/src/component/Review/SubReview.tsx +++ b/site/src/component/Review/SubReview.tsx @@ -9,6 +9,7 @@ import { Link } from 'react-router-dom'; import { PersonFill } from 'react-bootstrap-icons'; import { ReviewData, VoteRequest, CourseGQLData, ProfessorGQLData, VoteColor } from '../../types/types'; import ReportForm from '../ReportForm/ReportForm'; +import { FaPen } from 'react-icons/fa'; interface SubReviewProps { review: ReviewData; @@ -16,11 +17,15 @@ interface SubReviewProps { professor?: ProfessorGQLData; colors?: VoteColor; colorUpdater?: () => void; + editable?: boolean; + editReview?: (review: ReviewData, course?: CourseGQLData, professor?: ProfessorGQLData) => void; } -const SubReview: FC = ({ review, course, professor, colors, colorUpdater }) => { +const SubReview: FC = ({ review, course, professor, colors, colorUpdater, editable, editReview }) => { const [score, setScore] = useState(review.score); const [cookies] = useCookies(['user']); + + //Edit Review let upvoteClass; let downvoteClass; if (colors != undefined && colors.colors != undefined) { @@ -91,6 +96,11 @@ const SubReview: FC = ({ review, course, professor, colors, colo return (
+ {editable && editReview && ( +
editReview(review, course, professor)}> + +
+ )}

{professor && ( diff --git a/site/src/component/ReviewForm/ReviewForm.tsx b/site/src/component/ReviewForm/ReviewForm.tsx index 1f679800..0a85fdab 100644 --- a/site/src/component/ReviewForm/ReviewForm.tsx +++ b/site/src/component/ReviewForm/ReviewForm.tsx @@ -11,7 +11,6 @@ import Button from 'react-bootstrap/Button'; import RangeSlider from 'react-bootstrap-range-slider'; import Modal from 'react-bootstrap/Modal'; import ReCAPTCHA from 'react-google-recaptcha'; - import { addReview } from '../../store/slices/reviewSlice'; import { useAppDispatch, useAppSelector } from '../../store/hooks'; import { ReviewProps } from '../Review/Review'; @@ -21,6 +20,8 @@ import { quarterNames } from '../../helpers/planner'; interface ReviewFormProps extends ReviewProps { closeForm: () => void; + editable?: boolean; + review?: ReviewData; } const ReviewForm: FC = (props) => { @@ -44,7 +45,7 @@ const ReviewForm: FC = (props) => { 'Group projects', 'Gives good feedback', ]; - + const [reviewId, setReviewId] = useState(props.review?._id); //edit review const [professor, setProfessor] = useState(props.professor?.ucinetid || ''); const [course, setCourse] = useState(props.course?.id || ''); const [yearTaken, setYearTaken] = useState(''); @@ -82,19 +83,48 @@ const ReviewForm: FC = (props) => { if (cookies.user === undefined) { alert('You must be logged in to add a review!'); props.closeForm(); + } else { + setSubmitted(false); } } - }, [showForm, props, cookies]); + //If editable is true + if (props.review) { + const [year, quarter] = props.review.quarter.split(' '); + setReviewId(props.review?._id); + setQuarterTaken(quarter); + setYearTaken(year); + setGradeReceived(props.review.gradeReceived); + setDifficulty(props.review.difficulty); + setQuality(props.review.rating); + setContent(props.review?.reviewContent); + setSelectedTags(props.review?.tags); + setAttendance(props.review?.attendance); + setTakeAgain(props.review?.takeAgain); + setTextbook(props.review?.textbook); + setUserName(props.review?.userDisplay); + setProfessor(props.review?.professorID); + setCourse(props.review?.courseID); + } + }, [showForm]); const postReview = async (review: ReviewData) => { - const res = await axios.post('/api/reviews', review).catch((err) => err.response); - if (res.status === 400) { - alert(res.data.error ?? 'You have already submitted a review for this course/professor'); - } else if (res.data.error !== undefined) { - alert('You must be logged in to add a review!'); + if (props.editable) { + const res = await axios.patch('/api/reviews/updateReview', review); + if (res.data.hasOwnProperty.call(res.data, 'error')) { + alert('You must be logged in to edit the review!'); + } else { + setSubmitted(true); + } } else { - setSubmitted(true); - dispatch(addReview(res.data)); + const res = await axios.post('/api/reviews', review).catch((err) => err.response); + if (res.status === 400) { + alert(res.data.error ?? 'You have already submitted a review for this course/professor'); + } else if (res.data.error !== undefined) { + alert('You must be logged in to add a review!'); + } else { + setSubmitted(true); + dispatch(addReview(res.data)); + } } }; @@ -116,35 +146,69 @@ const ReviewForm: FC = (props) => { alert('Please complete the CAPTCHA'); return; } - - const date = new Date(); - const year = date.getFullYear(); - const month = (1 + date.getMonth()).toString(); - const day = date.getDate().toString(); - const review = { - professorID: professor, - courseID: course, - userID: userID, - userDisplay: userName, - reviewContent: content, - rating: quality, - difficulty: difficulty, - timestamp: month + '/' + day + '/' + year, - gradeReceived: gradeReceived, - forCredit: true, - quarter: yearTaken + ' ' + quarterTaken, - score: 0, - takeAgain: takeAgain, - textbook: textbook, - attendance: attendance, - tags: selectedTags, - captchaToken: captchaToken, - }; - if (content.length > 500) { - setOverCharLimit(true); + if (props.editable === false) { + const date = new Date(); + const year = date.getFullYear(); + const month = (1 + date.getMonth()).toString(); + const day = date.getDate().toString(); + const review = { + professorID: professor, + courseID: course, + userID: userID, + userDisplay: userName, + reviewContent: content, + rating: quality, + difficulty: difficulty, + timestamp: month + '/' + day + '/' + year, + gradeReceived: gradeReceived, + forCredit: true, + quarter: yearTaken + ' ' + quarterTaken, + score: 0, + takeAgain: takeAgain, + textbook: textbook, + attendance: attendance, + tags: selectedTags, + captchaToken: captchaToken, + }; + if (content.length > 500) { + setOverCharLimit(true); + } else { + setOverCharLimit(false); + postReview(review); + } } else { - setOverCharLimit(false); - postReview(review); + const date = new Date(); + const year = date.getFullYear(); + const month = (1 + date.getMonth()).toString(); + const day = date.getDate().toString(); + const review = { + _id: reviewId, + professorID: professor, + courseID: course, + userID: userID, + userDisplay: userName, + reviewContent: content, + rating: quality, + difficulty: difficulty, + timestamp: month + '/' + day + '/' + year, + gradeReceived: gradeReceived, + forCredit: true, + quarter: yearTaken + ' ' + quarterTaken, + score: 0, + takeAgain: takeAgain, + textbook: textbook, + attendance: attendance, + tags: selectedTags, + verified: false, + captchaToken: captchaToken, + }; + if (content.length > 500) { + setOverCharLimit(true); + } else { + setOverCharLimit(false); + postReview(review); + setSubmitted(true); + } } }; @@ -234,10 +298,14 @@ const ReviewForm: FC = (props) => { -

- It's your turn to review{' '} - {props.course ? props.course?.department + ' ' + props.course?.courseNumber : props.professor?.name} -

+ {props.editable ? ( +

Edit your review for {props.review?.courseID + ' ' + props.review?.professorID}

+ ) : ( +

+ It's your turn to review{' '} + {props.course ? props.course?.department + ' ' + props.course?.courseNumber : props.professor?.name} +

+ )}
@@ -254,6 +322,7 @@ const ReviewForm: FC = (props) => { defaultValue="" required onChange={(e) => setGradeReceived(e.target.value)} + value={gradeReceived} > @@ -407,6 +481,7 @@ const ReviewForm: FC = (props) => { setOverCharLimit(false); } }} + value={props.editable ? content : props.review?.reviewContent} /> {/*