Skip to content

Commit

Permalink
feat: Clean up, organize, and start common components
Browse files Browse the repository at this point in the history
  • Loading branch information
mathhulk committed Oct 15, 2024
1 parent 0a3bec1 commit c20082e
Show file tree
Hide file tree
Showing 44 changed files with 735 additions and 902 deletions.
7 changes: 0 additions & 7 deletions apps/backend/src/bootstrap/loaders/passport.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/**
* package.json override for oauth is to resolve package dependency issues in passport-google-oauth20 and
* passport-aouth2. Once these packages are updated, this override can be removed.
*
* Opened pull request: https://github.com/jaredhanson/passport-oauth2/pull/165
*/
import RedisStore from "connect-redis";
import type { Application } from "express";
import session from "express-session";
Expand Down Expand Up @@ -144,7 +138,6 @@ export default async (app: Application, redis: RedisClientType) => {
async (_, __, profile, done) => {
const email = profile.emails?.[0].value;

// null check for type safety
if (!email) {
return done(null, false, { message: "No email found" });
}
Expand Down
58 changes: 43 additions & 15 deletions apps/frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,22 @@ import Grades from "@/app/Grades";
import Landing from "@/app/Landing";
import Layout from "@/components/Layout";

const Class = {
Enrollment: lazy(() => import("@/components/Class/Enrollment")),
Grades: lazy(() => import("@/components/Class/Grades")),
Overview: lazy(() => import("@/components/Class/Overview")),
Sections: lazy(() => import("@/components/Class/Sections")),
};

const Course = {
Root: lazy(() => import("@/app/Course")),
Enrollment: lazy(() => import("@/components/Course/Enrollment")),
Grades: lazy(() => import("@/components/Course/Grades")),
Overview: lazy(() => import("@/components/Course/Overview")),
Classes: lazy(() => import("@/components/Course/Classes")),
};

const About = lazy(() => import("@/app/About"));
const CatalogEnrollment = lazy(() => import("@/components/Class/Enrollment"));
const CatalogGrades = lazy(() => import("@/components/Class/Grades"));
const CatalogOverview = lazy(() => import("@/components/Class/Overview"));
const CatalogSections = lazy(() => import("@/components/Class/Sections"));
const Discover = lazy(() => import("@/app/Discover"));
const Plan = lazy(() => import("@/app/Plan"));
const Schedule = lazy(() => import("@/app/Schedule"));
Expand All @@ -26,17 +37,12 @@ const Map = lazy(() => import("@/app/Map"));

const router = createBrowserRouter([
{
element: <Layout header={false} />,
element: <Layout header={false} footer={false} />,
children: [
{
element: <Discover />,
path: "discover",
},
],
},
{
element: <Layout header={false} footer={false} />,
children: [
{
element: <Landing />,
index: true,
Expand Down Expand Up @@ -81,24 +87,46 @@ const router = createBrowserRouter([
element: <Enrollment />,
path: "enrollment",
},
{
element: <Course.Root />,
path: "courses/:subject/:number",
children: [
{
element: <Course.Overview />,
index: true,
},
{
element: <Course.Classes />,
path: "classes",
},
{
element: <Course.Enrollment />,
path: "enrollment",
},
{
element: <Course.Grades />,
path: "grades",
},
],
},
{
element: <Catalog />,
path: "catalog/:year?/:semester?/:subject?/:courseNumber?/:classNumber?",
path: "catalog/:year?/:semester?/:subject?/:courseNumber?/:number?",
children: [
{
element: <CatalogOverview />,
element: <Class.Overview />,
index: true,
},
{
element: <CatalogSections />,
element: <Class.Sections />,
path: "sections",
},
{
element: <CatalogEnrollment />,
element: <Class.Enrollment />,
path: "enrollment",
},
{
element: <CatalogGrades />,
element: <Class.Grades />,
path: "grades",
},
],
Expand Down
167 changes: 71 additions & 96 deletions apps/frontend/src/app/Catalog/index.tsx
Original file line number Diff line number Diff line change
@@ -1,121 +1,105 @@
import { useCallback, useMemo, useState } from "react";

import { useQuery } from "@apollo/client";
import classNames from "classnames";
import { Xmark } from "iconoir-react";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { useLocation, useNavigate, useParams } from "react-router-dom";

import { Boundary, IconButton, LoadingIndicator } from "@repo/theme";
import { IconButton } from "@repo/theme";

import Class from "@/components/Class";
import ClassBrowser from "@/components/ClassBrowser";
import {
GET_CLASS,
GET_COURSE,
GET_TERMS,
GetClassResponse,
GetCourseResponse,
GetTermsResponse,
IClass,
TemporalPosition,
} from "@/lib/api";
import { useReadTerms } from "@/hooks/api";
import { useReadClass } from "@/hooks/api/classes/useReadClass";
import { Semester, TemporalPosition } from "@/lib/api";

import styles from "./Catalog.module.scss";
import Dashboard from "./Dashboard";

export default function Catalog() {
const {
year: currentYear,
semester: currentSemester,
subject: currentSubject,
year: providedYear,
semester: providedSemester,
subject: providedSubject,
courseNumber,
classNumber,
number,
} = useParams();

const navigate = useNavigate();
const [searchParams] = useSearchParams();
const location = useLocation();

const [expanded, setExpanded] = useState(true);
const [open, setOpen] = useState(false);
const [partialClass, setPartialClass] = useState<IClass | null>(null);

const { data, loading } = useQuery<GetTermsResponse>(GET_TERMS);
const { data: terms, loading: termsLoading } = useReadTerms();

const terms = useMemo(() => data?.terms, [data]);
const semester = useMemo(() => {
if (!providedSemester) return null;

const selectedTerm = useMemo(() => {
if (!currentYear || !currentSemester) return;
return providedSemester[0].toUpperCase() + providedSemester.slice(1);
}, [providedSemester]);

const semester =
currentSemester[0].toUpperCase() + currentSemester.slice(1);
const year = useMemo(() => {
if (!providedYear) return null;

return terms?.find(
(term) =>
term.year === parseInt(currentYear) && term.semester === semester
return parseInt(providedYear) || null;
}, [providedYear]);

const term = useMemo(() => {
if (!terms) return null;

const currentTerm = terms?.find(
(term) => term.temporalPosition === TemporalPosition.Current
);
}, [terms, currentYear, currentSemester]);

const currentTerm = useMemo(
() =>
selectedTerm ??
terms?.find((term) => term.temporalPosition === TemporalPosition.Current),
[terms]
);
// Default to the current term
return (
terms?.find((term) => term.year === year && term.semester === semester) ??
currentTerm
);
}, [terms, year, semester]);

const subject = useMemo(
() => currentSubject?.toUpperCase(),
[currentSubject]
() => providedSubject?.toUpperCase(),
[providedSubject]
);

const {
data: classData,
loading: classLoading,
error: classError,
} = useQuery<GetClassResponse>(GET_CLASS, {
variables: {
term: {
semester: currentTerm?.semester,
year: currentTerm?.year,
},
subject,
courseNumber,
classNumber,
},
skip: !subject || !courseNumber || !classNumber || !currentTerm,
});

// Fetch the course to for directing to the correct term
const { loading: courseLoading, error: courseError } =
useQuery<GetCourseResponse>(GET_COURSE, {
variables: {
subject,
courseNumber,
},
skip: !subject || !courseNumber,
});

const _class = useMemo(() => classData?.class, [classData]);

const handleClassSelect = useCallback(
(selectedClass: IClass) => {
if (!currentTerm) return;

setPartialClass(selectedClass);
const { data: _class, loading: classLoading } = useReadClass(
term?.year as number,
term?.semester as Semester,
subject as string,
courseNumber as string,
number as string,
{
skip: !subject || !courseNumber || !number || !term,
}
);

const handleSelect = useCallback(
(subject: string, courseNumber: string, number: string) => {
if (!term) return;

setOpen(true);

navigate({
pathname: `/catalog/${currentTerm.year}/${currentTerm.semester}/${selectedClass.course.subject}/${selectedClass.course.number}/${selectedClass.number}`,
search: searchParams.toString(),
...location,
pathname: `/catalog/${term.year}/${term.semester}/${subject}/${courseNumber}/${number}`,
});
},
[navigate, currentYear, currentSemester, searchParams, currentTerm]
[navigate, year, semester, location, term]
);

return loading ? (
<Boundary>
<LoadingIndicator size="lg" />
</Boundary>
) : currentTerm ? (
// TODO: Loading state
if (termsLoading) {
return <></>;
}

// TODO: Error state
if (!term) {
return <></>;
}

// TODO: Class error state, class loading state
return (
<div
className={classNames(styles.root, {
[styles.expanded]: expanded,
Expand All @@ -125,38 +109,31 @@ export default function Catalog() {
<div className={styles.panel}>
<div className={styles.header}>
<p className={styles.title}>
{currentTerm.semester} {currentTerm.year}
{term.semester} {term.year}
</p>
<IconButton onClick={() => setOpen(true)}>
<Xmark />
</IconButton>
</div>
<div className={styles.body}>
<ClassBrowser
onClassSelect={handleClassSelect}
semester={currentTerm.semester}
year={currentTerm.year}
onSelect={handleSelect}
semester={term.semester}
year={term.year}
persistent
/>
</div>
</div>
<div className={styles.view}>
{courseNumber && classNumber && subject && (_class || partialClass) ? (
{classLoading ? (
<></>
) : _class ? (
<Class
subject={subject}
courseNumber={courseNumber}
classNumber={classNumber}
partialClass={partialClass}
year={currentTerm.year}
semester={currentTerm.semester}
class={_class}
expanded={expanded}
onExpandedChange={setExpanded}
onClose={() => setOpen(false)}
/>
) : classLoading || courseLoading ? (
<>{/* Loading */}</>
) : classError || courseError ? (
<>{/* Error */}</>
) : (
<Dashboard
expanded={expanded}
Expand All @@ -166,7 +143,5 @@ export default function Catalog() {
)}
</div>
</div>
) : (
<>{/* Error */}</>
);
}
9 changes: 9 additions & 0 deletions apps/frontend/src/app/Course/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useParams } from "react-router-dom";

import Component from "@/components/Course";

export default function Course() {
const { subject, number } = useParams();

return <Component subject={subject as string} number={number as string} />;
}
Loading

0 comments on commit c20082e

Please sign in to comment.