diff --git a/apps/frontend/src/app/Catalog/Dashboard/index.tsx b/apps/frontend/src/app/Catalog/Dashboard/index.tsx index 6a9115c8e..43e27bc02 100644 --- a/apps/frontend/src/app/Catalog/Dashboard/index.tsx +++ b/apps/frontend/src/app/Catalog/Dashboard/index.tsx @@ -62,8 +62,11 @@ export default function Dashboard({ {terms .filter( - ({ year, semester }) => - year !== term.year || semester !== term.semester + ({ year, semester }, index) => + index === + terms.findIndex( + (term) => term.semester === semester && term.year === year + ) ) .map(({ year, semester }) => { return ( diff --git a/apps/frontend/src/components/Carousel/Carousel.module.scss b/apps/frontend/src/components/Carousel/Carousel.module.scss index a0aa1e882..ad59ef258 100644 --- a/apps/frontend/src/components/Carousel/Carousel.module.scss +++ b/apps/frontend/src/components/Carousel/Carousel.module.scss @@ -54,6 +54,7 @@ gap: 16px; scrollbar-width: none; scroll-behavior: smooth; + position: relative; } } diff --git a/apps/frontend/src/components/Carousel/Class/Class.module.scss b/apps/frontend/src/components/Carousel/Class/Class.module.scss index 26b164ce3..92a853e6d 100644 --- a/apps/frontend/src/components/Carousel/Class/Class.module.scss +++ b/apps/frontend/src/components/Carousel/Class/Class.module.scss @@ -1,60 +1,6 @@ -.root { - border: 1px solid var(--border-color); - border-radius: 8px; +.cont { flex-shrink: 0; - background-color: var(--foreground-color); - padding: 16px; - display: flex; width: 320px; gap: 16px; cursor: pointer; - - &:hover .column .icon { - color: var(--heading-color); - } - - .column { - display: flex; - flex-direction: column; - gap: 8px; - flex-shrink: 0; - - .icon { - color: var(--paragraph-color); - transition: all 100ms ease-in-out; - } - } - - .text { - flex-grow: 1; - font-size: 14px; - display: flex; - flex-direction: column; - - .heading { - color: var(--heading-color); - margin-bottom: 8px; - line-height: 1; - font-weight: 660; - font-feature-settings: - "cv05" on, - "cv13" on, - "ss07" on, - "cv12" on, - "cv06" on; - } - - .description { - color: var(--paragraph-color); - line-height: 1.5; - flex-grow: 1; - } - - .row { - display: flex; - gap: 12px; - margin-top: 12px; - align-items: center; - } - } -} +} \ No newline at end of file diff --git a/apps/frontend/src/components/Carousel/Class/index.tsx b/apps/frontend/src/components/Carousel/Class/index.tsx index 05ffab3f6..d36dcee0f 100644 --- a/apps/frontend/src/components/Carousel/Class/index.tsx +++ b/apps/frontend/src/components/Carousel/Class/index.tsx @@ -1,14 +1,6 @@ -import { useMemo } from "react"; - -import { ArrowRight } from "iconoir-react"; - -import AverageGrade from "@/components/AverageGrade"; -import Capacity from "@/components/Capacity"; -import Units from "@/components/Units"; -import { useReadClass } from "@/hooks/api"; +import ClassCard from "@/components/ClassCard"; import { Semester } from "@/lib/api"; -import ClassDrawer from "../../ClassDrawer"; import styles from "./Class.module.scss"; interface ClassProps { @@ -26,51 +18,71 @@ export default function Class({ courseNumber, number, }: ClassProps) { - const { data, loading } = useReadClass( - year as number, - semester as Semester, - subject as string, - courseNumber as string, - number as string + return ( +
+ +
); + // return
+ // + //
+ // const { data, loading } = useReadClass( + // year as number, + // semester as Semester, + // subject as string, + // courseNumber as string, + // number as string + // ); - const _class = useMemo(() => data, [data]); + // const _class = useMemo(() => data, [data]); - return loading || !_class ? ( - <> - ) : ( - -
-
-

- {_class.subject} {_class.courseNumber} #{_class.number} -

-

- {_class.title ?? _class.course.title} -

-
- - - -
-
-
-
- -
-
-
-
- ); + // return loading || !_class ? ( + // <> + // ) : ( + // + //
+ //
+ //

+ // {_class.subject} {_class.courseNumber} #{_class.number} + //

+ //

+ // {_class.title ?? _class.course.title} + //

+ //
+ // + // + // + //
+ //
+ //
+ //
+ // + //
+ //
+ //
+ //
+ // ); } diff --git a/apps/frontend/src/components/Class/index.tsx b/apps/frontend/src/components/Class/index.tsx index b3494865d..4e27ee51f 100644 --- a/apps/frontend/src/components/Class/index.tsx +++ b/apps/frontend/src/components/Class/index.tsx @@ -14,6 +14,7 @@ import { Bookmark, BookmarkSolid, CalendarPlus, + Expand, OpenBook, OpenNewWindow, Pin, @@ -342,7 +343,7 @@ export default function Class({ as={Link} to={`/catalog/${_class.year}/${_class.semester}/${_class.subject}/${_class.courseNumber}/${_class.number}`} > - + diff --git a/apps/frontend/src/components/ClassCard/ClassCard.module.scss b/apps/frontend/src/components/ClassCard/ClassCard.module.scss new file mode 100644 index 000000000..ecd9aae4e --- /dev/null +++ b/apps/frontend/src/components/ClassCard/ClassCard.module.scss @@ -0,0 +1,59 @@ +.root { + border: 1px solid var(--border-color); + border-radius: 8px; + flex-shrink: 0; + background-color: var(--foreground-color); + position: relative; + padding: 16px; + display: flex; + gap: 16px; + align-items: flex-start; + cursor: pointer; + height: 100%; + + &:hover .column .icon { + color: var(--heading-color); + } + + .column { + display: flex; + flex-direction: column; + gap: 8px; + flex-shrink: 0; + + .icon { + color: var(--paragraph-color); + transition: all 100ms ease-in-out; + } + } + + .text { + flex-grow: 1; + font-size: 14px; + + .heading { + color: var(--heading-color); + margin-bottom: 8px; + line-height: 1; + font-weight: 660; + font-feature-settings: + "cv05" on, + "cv13" on, + "ss07" on, + "cv12" on, + "cv06" on; + } + + .description { + color: var(--paragraph-color); + line-height: 1.5; + } + + .row { + display: flex; + gap: 12px; + margin-top: 12px; + align-items: center; + } + } +} diff --git a/apps/frontend/src/components/ClassCard/index.tsx b/apps/frontend/src/components/ClassCard/index.tsx new file mode 100644 index 000000000..f0b8292c1 --- /dev/null +++ b/apps/frontend/src/components/ClassCard/index.tsx @@ -0,0 +1,76 @@ +import { useMemo } from "react"; + +import { ArrowRight } from "iconoir-react"; + +import AverageGrade from "@/components/AverageGrade"; +import Capacity from "@/components/Capacity"; +import Units from "@/components/Units"; +import { useReadClass } from "@/hooks/api"; +import { Semester } from "@/lib/api"; + +import ClassDrawer from "../ClassDrawer"; +import styles from "./ClassCard.module.scss"; + +interface ClassProps { + year: number; + semester: Semester; + subject: string; + courseNumber: string; + number: string; +} + +export default function ClassCard({ + year, + semester, + subject, + courseNumber, + number, +}: ClassProps) { + const { data, loading } = useReadClass( + year as number, + semester as Semester, + subject as string, + courseNumber as string, + number as string + ); + + const _class = useMemo(() => data, [data]); + + return loading || !_class ? ( + <> + ) : ( + +
+
+

+ {_class.subject} {_class.courseNumber} #{_class.number} +

+

+ {_class.title ?? _class.course.title} +

+
+ + + +
+
+
+
+ +
+
+
+
+ ); +} diff --git a/apps/frontend/src/components/Course/Classes/Classes.module.scss b/apps/frontend/src/components/Course/Classes/Classes.module.scss new file mode 100644 index 000000000..55719e1f9 --- /dev/null +++ b/apps/frontend/src/components/Course/Classes/Classes.module.scss @@ -0,0 +1,14 @@ +.root { + width: 60%; + margin: auto; + .row { + margin-top: 30px; + display: flex; + } + .term { + flex: 30%; + color: var(--label-color); + text-align: center; + line-height: 121px; + } +} \ No newline at end of file diff --git a/apps/frontend/src/components/Course/Classes/index.tsx b/apps/frontend/src/components/Course/Classes/index.tsx index db77d5669..376d78455 100644 --- a/apps/frontend/src/components/Course/Classes/index.tsx +++ b/apps/frontend/src/components/Course/Classes/index.tsx @@ -1,18 +1,28 @@ +import ClassCard from "@/components/ClassCard"; import useCourse from "@/hooks/useCourse"; +import styles from "./Classes.module.scss"; + export default function Classes() { const { course } = useCourse(); return ( -
- {course.classes.map((_class, index) => ( -
-

- {_class.year} {_class.semester} -

-

{_class.title}

-
- ))} +
+ {course.classes && + course.classes.map((_class, index) => ( +
+
+ {_class.semester} {_class.year} +
+ +
+ ))}
); } diff --git a/apps/frontend/src/components/Course/Overview/Overview.module.scss b/apps/frontend/src/components/Course/Overview/Overview.module.scss new file mode 100644 index 000000000..341ff01c6 --- /dev/null +++ b/apps/frontend/src/components/Course/Overview/Overview.module.scss @@ -0,0 +1,17 @@ +.root { + padding: 24px 0; + font-size: 14px; + + .label { + margin-top: 24px; + color: var(--label-color); + line-height: 1; + } + + .description { + color: var(--paragraph-color); + margin-top: 8px; + line-height: 1.5; + } + } + \ No newline at end of file diff --git a/apps/frontend/src/components/Course/Overview/index.tsx b/apps/frontend/src/components/Course/Overview/index.tsx index 9c4e51835..8a7e3570c 100644 --- a/apps/frontend/src/components/Course/Overview/index.tsx +++ b/apps/frontend/src/components/Course/Overview/index.tsx @@ -1,12 +1,14 @@ import useCourse from "@/hooks/useCourse"; +import styles from "./Overview.module.scss"; + export default function Overview() { const { course } = useCourse(); return ( -
-

{course.title}

-

{course.description}

+
+

Description

+

{course.description}

); } diff --git a/apps/frontend/src/components/CourseCard/CourseCard.module.scss b/apps/frontend/src/components/CourseCard/CourseCard.module.scss new file mode 100644 index 000000000..cdb2eb15e --- /dev/null +++ b/apps/frontend/src/components/CourseCard/CourseCard.module.scss @@ -0,0 +1,59 @@ +.root { + border: 1px solid var(--border-color); + border-radius: 8px; + flex-shrink: 0; + background-color: var(--foreground-color); + position: relative; + padding: 16px; + display: flex; + gap: 16px; + align-items: flex-start; + cursor: pointer; + + &:hover .column .icon { + color: var(--heading-color); + } + + .column { + display: flex; + flex-direction: column; + gap: 8px; + flex-shrink: 0; + + .icon { + color: var(--paragraph-color); + transition: all 100ms ease-in-out; + } + } + + .text { + flex-grow: 1; + font-size: 14px; + + .heading { + color: var(--heading-color); + margin-bottom: 8px; + line-height: 1; + font-weight: 660; + font-feature-settings: + "cv05" on, + "cv13" on, + "ss07" on, + "cv12" on, + "cv06" on; + } + + .description { + color: var(--paragraph-color); + line-height: 1.5; + } + + .row { + display: flex; + gap: 12px; + margin-top: 12px; + align-items: center; + } + } + } + \ No newline at end of file diff --git a/apps/frontend/src/components/CourseCard/index.tsx b/apps/frontend/src/components/CourseCard/index.tsx new file mode 100644 index 000000000..cf8e4aa96 --- /dev/null +++ b/apps/frontend/src/components/CourseCard/index.tsx @@ -0,0 +1,43 @@ +import { useMemo } from "react"; + +import { ArrowRight } from "iconoir-react"; + +import { useReadCourse } from "@/hooks/api"; + +import AverageGrade from "../AverageGrade"; +import CourseDrawer from "../CourseDrawer"; +import styles from "./CourseCard.module.scss"; + +interface CourseProps { + subject: string; + number: string; +} + +export default function CourseCard({ subject, number }: CourseProps) { + const { data, loading } = useReadCourse(subject as string, number as string); + + const course = useMemo(() => data, [data]); + + return loading || !course ? ( + <> + ) : ( + +
+
+

+ {subject} {number} +

+

{course.title}

+
+ +
+
+
+
+ +
+
+
+
+ ); +} diff --git a/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss b/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss index ec1609476..38744c871 100644 --- a/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss +++ b/apps/frontend/src/components/Layout/PinsDrawer/PinsDrawer.module.scss @@ -54,7 +54,6 @@ body[data-theme="dark"] .overlay { padding: 12px; border-bottom: 1px solid var(--border-color); background-color: var(--foreground-color); - display: flex; gap: 12px; } } @@ -98,3 +97,25 @@ body[data-theme="dark"] .overlay { opacity: 0; } } +.exit { + position: absolute; + right: 12px; +} + +.menu { + margin-top: 20px; + button { + display: inline-block; + } +} + +.cardContainer { + padding: 0 15px; + margin-top: 15px; + .term { + margin-bottom: 10px; + margin-left: 10px; + font-size: 16px; + color: var(--label-color); + } +} \ No newline at end of file diff --git a/apps/frontend/src/components/Layout/PinsDrawer/index.tsx b/apps/frontend/src/components/Layout/PinsDrawer/index.tsx index ecad1845e..59eb075d1 100644 --- a/apps/frontend/src/components/Layout/PinsDrawer/index.tsx +++ b/apps/frontend/src/components/Layout/PinsDrawer/index.tsx @@ -1,11 +1,16 @@ import { ReactNode } from "react"; import * as Dialog from "@radix-ui/react-dialog"; +import * as Tabs from "@radix-ui/react-tabs"; import { Xmark, XmarkCircle } from "iconoir-react"; -import { Button, IconButton } from "@repo/theme"; +import { Button, IconButton, MenuItem } from "@repo/theme"; +import ClassCard from "@/components/ClassCard"; +import CourseCard from "@/components/CourseCard"; +import { Pin } from "@/contexts/PinsContext"; import usePins from "@/hooks/usePins"; +import { sortDescendingTerm } from "@/lib/class"; import styles from "./PinsDrawer.module.scss"; @@ -15,7 +20,7 @@ interface PinsDrawerProps { // TODO: Popover export default function PinsDrawer({ children }: PinsDrawerProps) { - const { pins } = usePins(); + const { pins, clearPins } = usePins(); return ( @@ -23,25 +28,76 @@ export default function PinsDrawer({ children }: PinsDrawerProps) { -
- - - - - - -
-
- {pins.map((pin) => ( -
- {pin.type} - {JSON.stringify(pin.data)} + +
+
+ + + + + +
- ))} -
+
+ + + Classes + + + Courses + + +
+
+
+ +
+ {/* TODO: Some kind of dropdown would be nice here */} + {pins + .filter((p) => p.type === "class") + .reverse() + .sort(sortDescendingTerm((p: Pin) => p.data)) + .map((pin) => ( +
+
+
+ {pin.data.semester} {pin.data.year} +
+ +
+
+ ))} +
+
+ +
+ {pins + .filter((p) => p.type === "course") + .reverse() + .sort(sortDescendingTerm((p: Pin) => p.data)) + .map((pin) => ( +
+
+ +
+
+ ))} +
+
+
+ diff --git a/apps/frontend/src/components/PinsProvider/index.tsx b/apps/frontend/src/components/PinsProvider/index.tsx index 65553d980..bfa303404 100644 --- a/apps/frontend/src/components/PinsProvider/index.tsx +++ b/apps/frontend/src/components/PinsProvider/index.tsx @@ -65,6 +65,10 @@ export default function PinsProvider({ children }: PinsProviderProps) { updatePins(pins.filter((existingPin) => existingPin.id !== pin.id)); }; + const clearPins = () => { + updatePins([]); + }; + const updatePins = (pins: Pin[]) => { setPins(pins); @@ -81,6 +85,7 @@ export default function PinsProvider({ children }: PinsProviderProps) { pinEventListeners, addPinEventListener, removePinEventListener, + clearPins, }} > {children} diff --git a/apps/frontend/src/contexts/PinsContext.ts b/apps/frontend/src/contexts/PinsContext.ts index 169133f9c..5abb4e1a0 100644 --- a/apps/frontend/src/contexts/PinsContext.ts +++ b/apps/frontend/src/contexts/PinsContext.ts @@ -38,6 +38,7 @@ export interface PinsContextType { pinEventListeners: PinEventListener[]; addPinEventListener: Dispatch; removePinEventListener: Dispatch; + clearPins: () => void; } const PinsContext = createContext(null); diff --git a/apps/frontend/src/lib/class.ts b/apps/frontend/src/lib/class.ts new file mode 100644 index 000000000..9928c63db --- /dev/null +++ b/apps/frontend/src/lib/class.ts @@ -0,0 +1,12 @@ +const SEMESTER_ORDER = ["Spring", "Summer", "Fall"]; + +export function sortDescendingTerm(objFetch: (data: any) => any) { + return (a: any, b: any) => { + const objA = objFetch(a); + const objB = objFetch(b); + return objA.year === objB.year + ? SEMESTER_ORDER.indexOf(objB.semester) - + SEMESTER_ORDER.indexOf(objA.semester) + : objB.year - objA.year; + }; +}