From c8e1706d641e431db2a048eaeb2c9c8eb39ad05f Mon Sep 17 00:00:00 2001 From: Pravin Rajah Date: Tue, 18 Nov 2025 02:59:27 -0800 Subject: [PATCH 1/3] Fixed original commit --- apps/frontend/src/App.tsx | 9 + .../ConfirmationPopups.module.scss | 29 ++ .../ConfirmationPopups/index.tsx | 66 ++++ .../NotificationClassCard.module.scss | 140 ++++++++ .../NotificationClassCard/index.tsx | 305 +++++++++++++++++ .../Notifications/Notifications.module.scss | 322 ++++++++++++++++++ .../src/app/Profile/Notifications/index.tsx | 295 ++++++++++++++++ apps/frontend/src/app/Profile/index.tsx | 13 + apps/frontend/src/lib/api/users.ts | 30 +- 9 files changed, 1208 insertions(+), 1 deletion(-) create mode 100644 apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/ConfirmationPopups.module.scss create mode 100644 apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/index.tsx create mode 100644 apps/frontend/src/app/Profile/Notifications/NotificationClassCard/NotificationClassCard.module.scss create mode 100644 apps/frontend/src/app/Profile/Notifications/NotificationClassCard/index.tsx create mode 100644 apps/frontend/src/app/Profile/Notifications/Notifications.module.scss create mode 100644 apps/frontend/src/app/Profile/Notifications/index.tsx diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx index 7e785e11d..8ace7c053 100644 --- a/apps/frontend/src/App.tsx +++ b/apps/frontend/src/App.tsx @@ -18,6 +18,7 @@ const Landing = lazy(() => import("@/app/Landing")); const Profile = { Root: lazy(() => import("@/app/Profile")), Account: lazy(() => import("@/app/Profile/Account")), + Notifications: lazy(() => import("@/app/Profile/Notifications")), Support: lazy(() => import("@/app/Profile/Support")), Ratings: lazy(() => import("@/app/Profile/Ratings")), Settings: lazy(() => import("@/app/Profile/Settings")), @@ -183,6 +184,14 @@ const router = createBrowserRouter([ ), index: true, }, + { + element: ( + + + + ), + path: "notifications", + }, { element: ( diff --git a/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/ConfirmationPopups.module.scss b/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/ConfirmationPopups.module.scss new file mode 100644 index 000000000..5c3a5fa0a --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/ConfirmationPopups.module.scss @@ -0,0 +1,29 @@ +.body { + align-items: center; + color: var(--heading-color); +} +.icon { + margin-top: 40px; + width: 70px; + height: 70px; + color: var(--blue-500); +} +.red { + color: var(--red-500); +} + +.title { + margin-top: 28px; + margin-bottom: 14px; + font-size: 28px; + font-weight: 600; +} + +.subtitle { + margin: 0px 20px; + color: var(--paragraph-color); + text-align: center; + font-size: 15px; + line-height: 1.5; + margin-bottom: 28px; +} diff --git a/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/index.tsx b/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/index.tsx new file mode 100644 index 000000000..2c561039a --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/ConfirmationPopups/index.tsx @@ -0,0 +1,66 @@ +import { useState } from "react"; + +import classNames from "classnames"; +import { + ArrowRight, + CheckCircleSolid, + WarningTriangleSolid, +} from "iconoir-react"; + +import { Button, Dialog } from "@repo/theme"; + +import styles from "./ConfirmationPopups.module.scss"; + +interface RemoveClassPopupProps { + isOpen: boolean; + onClose: () => void; + onConfirmRemove: () => Promise; +} + +export function RemoveClassPopup({ + isOpen, + onClose, + onConfirmRemove, +}: RemoveClassPopupProps) { + const [isRemoving, setIsRemoving] = useState(false); + + const handleRemove = async () => { + setIsRemoving(true); + try { + await onConfirmRemove(); + onClose(); + } finally { + setIsRemoving(false); + } + }; + + return ( + + + + + + +
Stop Tracking Class
+
+ You will no longer receive notifications about
+ enrollment changes for this class. +
+
+ + {!isRemoving && ( + + )} + + +
+
+
+ ); +} diff --git a/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/NotificationClassCard.module.scss b/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/NotificationClassCard.module.scss new file mode 100644 index 000000000..82bf14efd --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/NotificationClassCard.module.scss @@ -0,0 +1,140 @@ +.cardWrapper { + position: relative; + overflow: visible; + height: auto; + + &.popupOpen { + z-index: 100; + } + + > * { + height: 100% !important; + overflow: visible !important; + } +} + +.cardColumnHeader { + height: 100%; +} + +.cardBodyWrapper { + height: 100%; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.cardHeading { + font-size: 16px !important; + font-weight: 600 !important; + margin-bottom: 8px !important; + color: var(--heading-color); +} + +.sectionNumber { + color: var(--paragraph-color) !important; + font-weight: 600 !important; +} + +.description { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + color: var(--paragraph-color); +} + +.enrollmentText { + font-size: 14px; + font-weight: 400; + line-height: 1; +} + +.gradeWrapper { + padding-right: 4px; +} + +.bellWrapper { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + gap: 2px; + min-width: 28px; + height: 28px; + z-index: 100; + padding: 0 6px; + margin-top: -3.5px; + border: 1px solid var(--border-color); + border-radius: 4px; + transition: background-color 150ms ease-in-out; + cursor: pointer; + + &:hover { + background-color: var(--background-color); + } + + &.active { + color: var(--blue-500); + } +} + +.popup { + background-color: var(--foreground-color); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 8px; + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); + z-index: 10000; + width: 280px; + display: flex; + flex-direction: column; + position: absolute; + top: 45px; + right: 15px; + + .checkboxOption { + display: flex; + align-items: center; + gap: 10px; + padding: 6px 8px; + border-radius: 6px; + cursor: pointer; + + &:hover { + label { + color: var(--heading-color); + } + } + + label { + font-size: 14px; + color: var(--paragraph-color); + white-space: nowrap; + cursor: pointer; + transition: color 100ms ease-in-out; + } + + .checkbox { + width: 16px; + height: 16px; + display: grid; + place-items: center; + border-radius: 4px; + flex-shrink: 0; + + &[data-state="checked"] { + background-color: var(--blue-500); + color: white; + } + + &[data-state="unchecked"] { + border: 2px solid var(--paragraph-color); + } + } + } +} + +.arrowRotated { + transform: rotate(180deg); +} \ No newline at end of file diff --git a/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/index.tsx b/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/index.tsx new file mode 100644 index 000000000..4c3b518be --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/NotificationClassCard/index.tsx @@ -0,0 +1,305 @@ +import classNames from "classnames"; +import { + ComponentPropsWithRef, + useEffect, + useRef, + useState, +} from "react"; + +import { NavArrowDown, Check } from "iconoir-react"; +import { Checkbox } from "radix-ui"; +import { Card } from "@repo/theme"; + +import { AverageGrade } from "@/components/AverageGrade"; +import { getEnrollmentColor } from "@/components/Capacity"; +import Units from "@/components/Units"; +import { IClass } from "@/lib/api"; + +import { RemoveClassPopup } from "../ConfirmationPopups"; +import styles from "./NotificationClassCard.module.scss"; + +interface NotificationClassCardProps { + class: IClass; + thresholds: number[]; + onThresholdChange: (threshold: number, checked: boolean) => void; + onRemoveClass: () => Promise; + bookmarked?: boolean; + bookmarkToggle?: () => void; +} + +export default function NotificationClassCard({ + class: { + course: { + title: courseTitle, + subject: courseSubject, + number: courseNumber2, + gradeDistribution, + }, + title, + subject, + courseNumber, + number, + primarySection: { enrollment }, + unitsMax, + unitsMin, + }, + thresholds, + onThresholdChange, + onRemoveClass, + bookmarked = false, + bookmarkToggle, + ...props +}: NotificationClassCardProps & Omit, keyof NotificationClassCardProps>) { + const [showPopup, setShowPopup] = useState(false); + const [showRemoveConfirmation, setShowRemoveConfirmation] = useState(false); + const [tempThresholds, setTempThresholds] = useState(thresholds); + const popupRef = useRef(null); + const bellIconRef = useRef(null); + const isActive = thresholds.length > 0; + + useEffect(() => { + setTempThresholds(thresholds); + }, [thresholds]); + + + + const handleTempThresholdChange = (threshold: number, checked: boolean) => { + setTempThresholds((prev) => { + if (checked) { + return [...prev, threshold].sort((a, b) => a - b); + } else { + return prev.filter((t) => t !== threshold); + } + }); + }; + + const applyThresholdChanges = () => { + tempThresholds.forEach((threshold) => { + if (!thresholds.includes(threshold)) { + onThresholdChange(threshold, true); + } + }); + thresholds.forEach((threshold) => { + if (!tempThresholds.includes(threshold)) { + onThresholdChange(threshold, false); + } + }); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + popupRef.current && + bellIconRef.current && + !popupRef.current.contains(event.target as Node) && + !bellIconRef.current.contains(event.target as Node) + ) { + if (tempThresholds.length === 0 && thresholds.length > 0) { + setShowRemoveConfirmation(true); + setShowPopup(false); + } else { + applyThresholdChanges(); + setShowPopup(false); + } + } + }; + + if (showPopup) { + document.addEventListener("mousedown", handleClickOutside); + } + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [showPopup, tempThresholds, thresholds, onThresholdChange]); + + const enrolled = enrollment?.latest.enrolledCount ?? 0; + const max = enrollment?.latest.maxEnroll ?? 0; + const enrollmentPercentage = max > 0 ? Math.round((enrolled / max) * 100) : 0; + const enrollmentColor = getEnrollmentColor(enrolled, max); + + const uniqueId = `${subject}-${number}`; + + return ( +
+ + + + + +
+
+ + {subject ?? courseSubject} {courseNumber ?? courseNumber2} + #{number} + + {title ?? courseTitle} +
+ + + {enrollmentPercentage}% enrolled + + + +
+ +
+ + {gradeDistribution && ( +
+ +
+ )} +
setShowPopup(!showPopup)} + > + {isActive ? ( + <> + + + + + ) : ( + + + )} +
+ + {showPopup && ( +
+
+ handleTempThresholdChange(50, checked as boolean)} + > + + + + + +
+ +
+ handleTempThresholdChange(75, checked as boolean)} + > + + + + + +
+ +
+ handleTempThresholdChange(90, checked as boolean)} + > + + + + + +
+
+ )} + +
+
+
+ { + setTempThresholds(thresholds); + setShowRemoveConfirmation(false); + }} + onConfirmRemove={async () => { + await onRemoveClass(); + setShowRemoveConfirmation(false); + }} + /> +
+ ); +} + + +const CustomBellNotificationIcon = (props: React.SVGProps) => ( + + + + + +); + +const CustomBellIcon = (props: React.SVGProps) => ( + + + + +); diff --git a/apps/frontend/src/app/Profile/Notifications/Notifications.module.scss b/apps/frontend/src/app/Profile/Notifications/Notifications.module.scss new file mode 100644 index 000000000..dbabdcf18 --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/Notifications.module.scss @@ -0,0 +1,322 @@ +.container { + max-width: 100%; +} + +.header { + margin-bottom: 32px; + + h1 { + font-size: 32px; + font-weight: 700; + margin: 0 0 24px 0; + color: var(--heading-color); + } +} + +.subtitle { + font-size: 16px; + color: #64748B; + line-height: 1.6; + margin: 0 0 40px 0; + max-width: 80%; +} + +.section { + margin-bottom: 48px; + + h2 { + font-size: 24px; + font-weight: 600; + margin: 0 0 24px 0; + color: var(--heading-color); + max-width: 80%; + } +} + +.sectionDescription { + font-size: 16px; + color: #64748B; + /* Remaining styles */ + line-height: 1.6; + margin: 0 0 20px 0; +} + +.emptyState { + border: 1px solid var(--border-color); + border-radius: 8px; + background-color: var(--foreground-color); + height: 200px; + color: var(--label-color); +} + +.classGrid { + display: grid; + grid-template-columns: repeat(auto-fill, 326px); + gap: 20px; + /* ... */ +} + +.classCard { + border: 1px solid var(--border-color); + border-radius: 8px; + background-color: var(--foreground-color); + overflow: hidden; + transition: box-shadow 150ms ease-in-out; + padding: 16px; + /* ... */ +} + +.cardHeader { +} + +.classTitle { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 8px; + + h3 { + margin: 0; + font-size: 16px; + font-weight: 600; + color: var(--paragraph-color); + } +} + +.classActions { + display: flex; + align-items: center; + gap: 8px; +} + +.grade { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 32px; + height: 24px; + padding: 0 8px; + background-color: var(--green-100); + color: var(--green-700); + border-radius: 4px; + font-size: 13px; + font-weight: 600; +} + +.bookmarkIcon { + width: 20px; + height: 20px; + color: var(--paragraph-color); + cursor: pointer; + transition: color 100ms ease-in-out; + + &:hover { + color: var(--blue-500); + } +} + +.dropdown { + position: relative; +} + +.bellIcon { + width: 20px; + height: 20px; + color: var(--blue-500); + cursor: pointer; + transition: color 100ms ease-in-out; + + &:hover { + color: var(--blue-600); + } +} + +.courseName { + margin: 0 0 8px 0; + font-size: 14px; + color: #64748B !important; + line-height: 1.4; + white-space: nowrap !important; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; +} + +.enrollment { + display: flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--paragraph-color); +} + +.enrollmentIcon { + font-size: 14px; +} + +.units { + margin-left: auto; + color: var(--paragraph-color); +} + +.notificationOptions { + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; +} + +.checkboxOption { + display: flex; + align-items: center; + gap: 10px; + font-size: 14px; + color: var(--paragraph-color); + cursor: pointer; + user-select: none; + + input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + flex-shrink: 0; + } + + span { + line-height: 1.4; + } + + &:hover { + color: var(--paragraph-color); + } +} + +.stopTrackingContainer { + display: flex; + justify-content: flex-end; + margin-top: 16px; +} + +.stopTrackingButton { + padding: 10px 20px; + background-color: transparent; + border: 1px solid var(--border-color); + border-radius: 6px; + color: var(--red-500); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 100ms ease-in-out; + + &:hover { + background-color: var(--red-50); + border-color: var(--red-500); + } +} + +.toggleOptions { + display: flex; + flex-direction: column; + gap: 16px; +} + +.toggleOption { + display: flex; + align-items: center; + gap: 12px; + font-size: 16px; + color: #64748B; + cursor: pointer; + user-select: none; + + input[type="checkbox"] { + width: 44px; + height: 24px; + cursor: pointer; + appearance: none; + background-color: var(--border-color); + border-radius: 12px; + position: relative; + transition: background-color 150ms ease-in-out; + flex-shrink: 0; + + &::before { + content: ""; + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: white; + top: 2px; + left: 2px; + transition: transform 150ms ease-in-out; + } + + &:checked { + background-color: var(--blue-500); + + &::before { + transform: translateX(20px); + } + } + } + + span { + line-height: 1.4; + } +} + +.toggleHeader { + display: flex; + align-items: center; + justify-content: space-between; + + h2 { + margin: 0; + } +} + +.switch { + position: relative; + display: inline-block; + width: 44px; + height: 24px; + + input[type="checkbox"] { + opacity: 0; + width: 0; + height: 0; + + &:checked + .slider { + background-color: var(--blue-500); + + &::before { + transform: translateX(20px); + } + } + } +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--border-color); + border-radius: 12px; + transition: background-color 150ms ease-in-out; + + &::before { + content: ""; + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: white; + top: 2px; + left: 2px; + transition: transform 150ms ease-in-out; + } +} diff --git a/apps/frontend/src/app/Profile/Notifications/index.tsx b/apps/frontend/src/app/Profile/Notifications/index.tsx new file mode 100644 index 000000000..4ca18902c --- /dev/null +++ b/apps/frontend/src/app/Profile/Notifications/index.tsx @@ -0,0 +1,295 @@ +import { useCallback, useState } from "react"; +import { Text } from "@repo/theme"; + +import { useReadUser, useUpdateUser } from "@/hooks/api"; +import { IMonitoredClass } from "@/lib/api/users"; + +import NotificationClassCard from "./NotificationClassCard"; +import styles from "./Notifications.module.scss"; + +// Test data for development +const TEST_DATA: IMonitoredClass[] = [ + { + class: { + title: "Foundations of the U.S Air Force", + subject: "AEROSPC", + number: "1A", + courseNumber: "1A", + year: 2024, + semester: "Spring", + sessionId: null, + unitsMin: 1, + unitsMax: 1, + course: { + title: "Foundations of U.S Air Force", + subject: "AEROSPC", + number: "1A", + gradeDistribution: { + average: "A+", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 0, + maxEnroll: 60, + waitlistedCount: 0, + maxWaitlist: 0, + }, + }, + }, + gradeDistribution: { + average: "A+", + }, + } as any, + thresholds: [50], + }, + { + class: { + title: "Intro to Computer Science", + subject: "COMPSCI", + number: "61A", + year: 2024, + semester: "Fall", + sessionId: null, + unitsMin: 4, + unitsMax: 4, + course: { + title: "Intro to Computer Science", + subject: "COMPSCI", + number: "61A", + gradeDistribution: { + average: "B+", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 450, + maxEnroll: 500, + waitlistedCount: 25, + maxWaitlist: 50, + }, + }, + }, + gradeDistribution: { + average: "B+", + }, + } as any, + thresholds: [50, 75, 90], + }, + { + class: { + title: "Calculus", + subject: "MATH", + number: "1A", + year: 2024, + semester: "Spring", + sessionId: null, + unitsMin: 4, + unitsMax: 4, + course: { + title: "Calculus", + subject: "MATH", + gradeDistribution: { + average: "B", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 200, + maxEnroll: 250, + waitlistedCount: 10, + maxWaitlist: 30, + }, + }, + }, + gradeDistribution: { + average: "B", + }, + } as any, + thresholds: [75, 100], + }, +]; + +export default function Notifications() { + const { data: user } = useReadUser(); + const [updateUser] = useUpdateUser(); + + const [monitoredClasses, setMonitoredClasses] = useState( + user?.monitoredClasses && user.monitoredClasses.length > 0 + ? user.monitoredClasses + : TEST_DATA + ); + + const [addDropDeadline, setAddDropDeadline] = useState(false); + const [lateChangeSchedule, setLateChangeSchedule] = useState(false); + const [receiveEmails, setReceiveEmails] = useState(true); + + const handleThresholdChange = ( + classIndex: number, + threshold: number, + checked: boolean + ) => { + const updated = [...monitoredClasses]; + const currentThresholds = updated[classIndex].thresholds; + + if (checked) { + updated[classIndex].thresholds = [...currentThresholds, threshold].sort( + (a, b) => a - b + ); + } else { + updated[classIndex].thresholds = currentThresholds.filter( + (t) => t !== threshold + ); + } + + setMonitoredClasses(updated); + // TODO: Call mutation to update user preferences + console.log( + "Update thresholds for class:", + classIndex, + updated[classIndex].thresholds + ); + }; + + const handleRemoveClass = async (classIndex: number) => { + const updated = monitoredClasses.filter((_, index) => index !== classIndex); + setMonitoredClasses(updated); + // TODO: Call mutation to update user monitoredClasses + console.log("Removed class at index:", classIndex); + }; + + const bookmark = useCallback( + async (classToBookmark: any) => { + if (!user || !classToBookmark) return; + + const bookmarked = user.bookmarkedClasses.some( + (bookmarkedClass) => + bookmarkedClass.subject === classToBookmark.subject && + bookmarkedClass.courseNumber === classToBookmark.courseNumber && + bookmarkedClass.number === classToBookmark.number && + bookmarkedClass.year === classToBookmark.year && + bookmarkedClass.semester === classToBookmark.semester + ); + + const bookmarkedClasses = bookmarked + ? user.bookmarkedClasses.filter( + (bookmarkedClass) => + !( + bookmarkedClass.subject === classToBookmark.subject && + bookmarkedClass.courseNumber === classToBookmark.courseNumber && + bookmarkedClass.number === classToBookmark.number && + bookmarkedClass.year === classToBookmark.year && + bookmarkedClass.semester === classToBookmark.semester + ) + ) + : [...user.bookmarkedClasses, classToBookmark]; + + const payload = { + bookmarkedClasses: bookmarkedClasses.map((bookmarkedClass) => ({ + subject: bookmarkedClass.subject, + number: bookmarkedClass.number, + courseNumber: bookmarkedClass.courseNumber, + year: bookmarkedClass.year, + semester: bookmarkedClass.semester, + sessionId: bookmarkedClass.sessionId || "1", // Default to "1" if null + })), + }; + + await updateUser(payload); + }, + [user, updateUser] + ); + + return ( +
+
+

Course Enrollment Notifications

+

+ Manage the classes you are tracking by setting specific alerts for + enrollment thresholds. Notifications will be delivered to your + registered @berkeley.edu email address. +

+
+ +
+
+ +
+
+ +
+

Classes You're Tracking

+ + {monitoredClasses.length === 0 ? null : ( +
+ {monitoredClasses.map((monitoredClass, index) => { + const isBookmarked = user?.bookmarkedClasses.some( + (bookmarkedClass) => + bookmarkedClass.subject === monitoredClass.class.subject && + bookmarkedClass.courseNumber === + monitoredClass.class.courseNumber && + bookmarkedClass.number === monitoredClass.class.number && + bookmarkedClass.year === monitoredClass.class.year && + bookmarkedClass.semester === monitoredClass.class.semester + ); + + return ( + + handleThresholdChange(index, threshold, checked) + } + onRemoveClass={async () => await handleRemoveClass(index)} + bookmarked={isBookmarked} + bookmarkToggle={() => bookmark(monitoredClass.class)} + /> + ); + })} +
+ )} +
+ +
+

Add/Drop Deadline Notifications

+ + Get notified about key academic deadlines, including add/drop and late + change of class schedule for the semester. + + +
+ + +
+
+
+ ); +} diff --git a/apps/frontend/src/app/Profile/index.tsx b/apps/frontend/src/app/Profile/index.tsx index 89d726f97..4a04a0ad4 100644 --- a/apps/frontend/src/app/Profile/index.tsx +++ b/apps/frontend/src/app/Profile/index.tsx @@ -1,5 +1,6 @@ import classNames from "classnames"; import { + Bell, ChatBubbleQuestion, LogOut, ProfileCircle, @@ -41,6 +42,18 @@ export default function Root() { )} + + {({ isActive }) => ( +
+ + Notifications +
+ )} +
{({ isActive }) => (
Date: Tue, 18 Nov 2025 03:24:54 -0800 Subject: [PATCH 2/3] Commented out test data --- .../src/app/Profile/Notifications/index.tsx | 208 +++++++++--------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/apps/frontend/src/app/Profile/Notifications/index.tsx b/apps/frontend/src/app/Profile/Notifications/index.tsx index 4ca18902c..bbb7b707d 100644 --- a/apps/frontend/src/app/Profile/Notifications/index.tsx +++ b/apps/frontend/src/app/Profile/Notifications/index.tsx @@ -8,110 +8,110 @@ import NotificationClassCard from "./NotificationClassCard"; import styles from "./Notifications.module.scss"; // Test data for development -const TEST_DATA: IMonitoredClass[] = [ - { - class: { - title: "Foundations of the U.S Air Force", - subject: "AEROSPC", - number: "1A", - courseNumber: "1A", - year: 2024, - semester: "Spring", - sessionId: null, - unitsMin: 1, - unitsMax: 1, - course: { - title: "Foundations of U.S Air Force", - subject: "AEROSPC", - number: "1A", - gradeDistribution: { - average: "A+", - }, - }, - primarySection: { - enrollment: { - latest: { - enrolledCount: 0, - maxEnroll: 60, - waitlistedCount: 0, - maxWaitlist: 0, - }, - }, - }, - gradeDistribution: { - average: "A+", - }, - } as any, - thresholds: [50], - }, - { - class: { - title: "Intro to Computer Science", - subject: "COMPSCI", - number: "61A", - year: 2024, - semester: "Fall", - sessionId: null, - unitsMin: 4, - unitsMax: 4, - course: { - title: "Intro to Computer Science", - subject: "COMPSCI", - number: "61A", - gradeDistribution: { - average: "B+", - }, - }, - primarySection: { - enrollment: { - latest: { - enrolledCount: 450, - maxEnroll: 500, - waitlistedCount: 25, - maxWaitlist: 50, - }, - }, - }, - gradeDistribution: { - average: "B+", - }, - } as any, - thresholds: [50, 75, 90], - }, - { - class: { - title: "Calculus", - subject: "MATH", - number: "1A", - year: 2024, - semester: "Spring", - sessionId: null, - unitsMin: 4, - unitsMax: 4, - course: { - title: "Calculus", - subject: "MATH", - gradeDistribution: { - average: "B", - }, - }, - primarySection: { - enrollment: { - latest: { - enrolledCount: 200, - maxEnroll: 250, - waitlistedCount: 10, - maxWaitlist: 30, - }, - }, - }, - gradeDistribution: { - average: "B", - }, - } as any, - thresholds: [75, 100], - }, -]; +// const TEST_DATA: IMonitoredClass[] = [ +// { +// class: { +// title: "Foundations of the U.S Air Force", +// subject: "AEROSPC", +// number: "1A", +// courseNumber: "1A", +// year: 2024, +// semester: "Spring", +// sessionId: null, +// unitsMin: 1, +// unitsMax: 1, +// course: { +// title: "Foundations of U.S Air Force", +// subject: "AEROSPC", +// number: "1A", +// gradeDistribution: { +// average: "A+", +// }, +// }, +// primarySection: { +// enrollment: { +// latest: { +// enrolledCount: 0, +// maxEnroll: 60, +// waitlistedCount: 0, +// maxWaitlist: 0, +// }, +// }, +// }, +// gradeDistribution: { +// average: "A+", +// }, +// } as any, +// thresholds: [50], +// }, +// { +// class: { +// title: "Intro to Computer Science", +// subject: "COMPSCI", +// number: "61A", +// year: 2024, +// semester: "Fall", +// sessionId: null, +// unitsMin: 4, +// unitsMax: 4, +// course: { +// title: "Intro to Computer Science", +// subject: "COMPSCI", +// number: "61A", +// gradeDistribution: { +// average: "B+", +// }, +// }, +// primarySection: { +// enrollment: { +// latest: { +// enrolledCount: 450, +// maxEnroll: 500, +// waitlistedCount: 25, +// maxWaitlist: 50, +// }, +// }, +// }, +// gradeDistribution: { +// average: "B+", +// }, +// } as any, +// thresholds: [50, 75, 90], +// }, +// { +// class: { +// title: "Calculus", +// subject: "MATH", +// number: "1A", +// year: 2024, +// semester: "Spring", +// sessionId: null, +// unitsMin: 4, +// unitsMax: 4, +// course: { +// title: "Calculus", +// subject: "MATH", +// gradeDistribution: { +// average: "B", +// }, +// }, +// primarySection: { +// enrollment: { +// latest: { +// enrolledCount: 200, +// maxEnroll: 250, +// waitlistedCount: 10, +// maxWaitlist: 30, +// }, +// }, +// }, +// gradeDistribution: { +// average: "B", +// }, +// } as any, +// thresholds: [75, 100], +// }, +// ]; export default function Notifications() { const { data: user } = useReadUser(); From 8d689f4e8e37f93087a645e4f443c08bbac59304 Mon Sep 17 00:00:00 2001 From: Pravin Rajah Date: Tue, 18 Nov 2025 03:35:23 -0800 Subject: [PATCH 3/3] added test data back --- .../src/app/Profile/Notifications/index.tsx | 208 +++++++++--------- 1 file changed, 104 insertions(+), 104 deletions(-) diff --git a/apps/frontend/src/app/Profile/Notifications/index.tsx b/apps/frontend/src/app/Profile/Notifications/index.tsx index bbb7b707d..4ca18902c 100644 --- a/apps/frontend/src/app/Profile/Notifications/index.tsx +++ b/apps/frontend/src/app/Profile/Notifications/index.tsx @@ -8,110 +8,110 @@ import NotificationClassCard from "./NotificationClassCard"; import styles from "./Notifications.module.scss"; // Test data for development -// const TEST_DATA: IMonitoredClass[] = [ -// { -// class: { -// title: "Foundations of the U.S Air Force", -// subject: "AEROSPC", -// number: "1A", -// courseNumber: "1A", -// year: 2024, -// semester: "Spring", -// sessionId: null, -// unitsMin: 1, -// unitsMax: 1, -// course: { -// title: "Foundations of U.S Air Force", -// subject: "AEROSPC", -// number: "1A", -// gradeDistribution: { -// average: "A+", -// }, -// }, -// primarySection: { -// enrollment: { -// latest: { -// enrolledCount: 0, -// maxEnroll: 60, -// waitlistedCount: 0, -// maxWaitlist: 0, -// }, -// }, -// }, -// gradeDistribution: { -// average: "A+", -// }, -// } as any, -// thresholds: [50], -// }, -// { -// class: { -// title: "Intro to Computer Science", -// subject: "COMPSCI", -// number: "61A", -// year: 2024, -// semester: "Fall", -// sessionId: null, -// unitsMin: 4, -// unitsMax: 4, -// course: { -// title: "Intro to Computer Science", -// subject: "COMPSCI", -// number: "61A", -// gradeDistribution: { -// average: "B+", -// }, -// }, -// primarySection: { -// enrollment: { -// latest: { -// enrolledCount: 450, -// maxEnroll: 500, -// waitlistedCount: 25, -// maxWaitlist: 50, -// }, -// }, -// }, -// gradeDistribution: { -// average: "B+", -// }, -// } as any, -// thresholds: [50, 75, 90], -// }, -// { -// class: { -// title: "Calculus", -// subject: "MATH", -// number: "1A", -// year: 2024, -// semester: "Spring", -// sessionId: null, -// unitsMin: 4, -// unitsMax: 4, -// course: { -// title: "Calculus", -// subject: "MATH", -// gradeDistribution: { -// average: "B", -// }, -// }, -// primarySection: { -// enrollment: { -// latest: { -// enrolledCount: 200, -// maxEnroll: 250, -// waitlistedCount: 10, -// maxWaitlist: 30, -// }, -// }, -// }, -// gradeDistribution: { -// average: "B", -// }, -// } as any, -// thresholds: [75, 100], -// }, -// ]; +const TEST_DATA: IMonitoredClass[] = [ + { + class: { + title: "Foundations of the U.S Air Force", + subject: "AEROSPC", + number: "1A", + courseNumber: "1A", + year: 2024, + semester: "Spring", + sessionId: null, + unitsMin: 1, + unitsMax: 1, + course: { + title: "Foundations of U.S Air Force", + subject: "AEROSPC", + number: "1A", + gradeDistribution: { + average: "A+", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 0, + maxEnroll: 60, + waitlistedCount: 0, + maxWaitlist: 0, + }, + }, + }, + gradeDistribution: { + average: "A+", + }, + } as any, + thresholds: [50], + }, + { + class: { + title: "Intro to Computer Science", + subject: "COMPSCI", + number: "61A", + year: 2024, + semester: "Fall", + sessionId: null, + unitsMin: 4, + unitsMax: 4, + course: { + title: "Intro to Computer Science", + subject: "COMPSCI", + number: "61A", + gradeDistribution: { + average: "B+", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 450, + maxEnroll: 500, + waitlistedCount: 25, + maxWaitlist: 50, + }, + }, + }, + gradeDistribution: { + average: "B+", + }, + } as any, + thresholds: [50, 75, 90], + }, + { + class: { + title: "Calculus", + subject: "MATH", + number: "1A", + year: 2024, + semester: "Spring", + sessionId: null, + unitsMin: 4, + unitsMax: 4, + course: { + title: "Calculus", + subject: "MATH", + gradeDistribution: { + average: "B", + }, + }, + primarySection: { + enrollment: { + latest: { + enrolledCount: 200, + maxEnroll: 250, + waitlistedCount: 10, + maxWaitlist: 30, + }, + }, + }, + gradeDistribution: { + average: "B", + }, + } as any, + thresholds: [75, 100], + }, +]; export default function Notifications() { const { data: user } = useReadUser();