diff --git a/apps/frontend/src/app/api/geneds.ts b/apps/frontend/src/app/api/geneds.ts index 152f1187..1882c128 100644 --- a/apps/frontend/src/app/api/geneds.ts +++ b/apps/frontend/src/app/api/geneds.ts @@ -1,38 +1,30 @@ -import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RootState } from "~/store"; +import axios from "axios"; import { Gened } from "~/app/types"; +import { GetToken } from "@clerk/types"; -export const fetchGenedsBySchool = createAsyncThunk< - Gened[], - string, - { state: RootState } ->("fetchGenedsBySchool", async (school: string, thunkAPI) => { - const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/geneds?`; - const state = thunkAPI.getState(); +export const fetchGenedsBySchool = async (selectedSchool: string, isSignedIn: boolean | undefined, getToken: GetToken): Promise => { + const url = `${process.env.NEXT_PUBLIC_BACKEND_URL || ""}/geneds`; const params = new URLSearchParams(); - params.append("school", school); + params.append("school", selectedSchool); - if (state.user.loggedIn && state.user.token) { - return ( - await fetch(url + params.toString(), { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - token: state.user.token, - }), - }) - ).json(); + const token = await getToken() + + if (isSignedIn && token) { + const response = await axios.post(url, { token }, { + headers: { + "Content-Type": "application/json", + }, + params, + }); + return response.data; } else { - return ( - await fetch(url + params.toString(), { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }) - ).json(); + const response = await axios.get(url, { + headers: { + "Content-Type": "application/json", + }, + params, + }); + return response.data; } -}); +}; \ No newline at end of file diff --git a/apps/frontend/src/app/cache.ts b/apps/frontend/src/app/cache.ts index 0010ee46..86f70776 100644 --- a/apps/frontend/src/app/cache.ts +++ b/apps/frontend/src/app/cache.ts @@ -11,7 +11,6 @@ import { import { fetchFCEInfosByCourse, fetchFCEInfosByInstructor } from "./api/fce"; import { fetchAllInstructors } from "./api/instructors"; import Fuse, { FuseIndex } from "fuse.js"; -import { fetchGenedsBySchool } from "./api/geneds"; /** * This cache lasts for the duration of the user session @@ -37,7 +36,6 @@ interface CacheState { instructorsLoading: boolean; instructorPage: number; selectedInstructors: { instructor: string }[]; - geneds: Gened[]; } const initialState: CacheState = { @@ -59,7 +57,6 @@ const initialState: CacheState = { instructorsLoading: false, instructorPage: 1, selectedInstructors: [], - geneds: [], }; export const selectCourseResults = @@ -236,13 +233,6 @@ export const cacheSlice = createSlice({ state.selectedInstructors = action.payload; } }); - - builder - .addCase(fetchGenedsBySchool.fulfilled, (state, action) => { - if (action.payload) { - state.geneds = action.payload; - } - }); }, }); diff --git a/apps/frontend/src/components/GenedsViewer.tsx b/apps/frontend/src/components/GenedsViewer.tsx index f9c43b11..f36a6dc2 100644 --- a/apps/frontend/src/components/GenedsViewer.tsx +++ b/apps/frontend/src/components/GenedsViewer.tsx @@ -8,40 +8,79 @@ import { ChevronUpDownIcon, XMarkIcon } from "@heroicons/react/24/outline"; import { Gened } from "~/app/types"; import { classNames } from "~/app/utils"; import { CheckIcon } from "@heroicons/react/20/solid"; -import { SignInButton } from "@clerk/nextjs"; +import { SignInButton, useAuth } from "@clerk/nextjs"; import { userSlice } from "~/app/user"; import { GENED_SCHOOLS, GENED_SOURCES } from "~/app/constants"; import Link from "~/components/Link"; +import { useQuery } from "@tanstack/react-query"; const GenedsViewer = () => { const dispatch = useAppDispatch(); - const loggedIn = useAppSelector((state) => state.user.loggedIn); - const geneds = useAppSelector((state) => state.cache.geneds); const selectedSchool = useAppSelector((state) => state.user.selectedSchool); const selectedTags = useAppSelector((state) => state.user.selectedTags); const aggregationOptions = useAppSelector((state) => state.user.fceAggregation); + const { isSignedIn, getToken } = useAuth(); const [tagQuery, setTagQuery] = useState(""); const [tags, setTags] = useState([]); const [data, setData] = useState([]); - const [query, setQuery] = useState(""); + const [searchQuery, setSearchQuery] = useState(""); + + const {isPending, error, data: geneds} = useQuery({ + queryKey: ['geneds', selectedSchool, isSignedIn], + queryFn: () => fetchGenedsBySchool(selectedSchool, isSignedIn, getToken) + }); const deleteTag = (tagToDelete: string) => { dispatch(userSlice.actions.setSelectedTags(selectedTags.filter((tag) => tag !== tagToDelete))); }; - useEffect(() => { - if (selectedSchool) - void dispatch(fetchGenedsBySchool(selectedSchool)); - }, [dispatch, loggedIn, selectedSchool]); + const getGenedTable = () => { + if (isPending) { + return ( +
+ Loading... +
+ ); + } + + if (error) { + console.log(error.message); + + return ( +
+ Sorry there was an error in displaying this, please refresh! +
+ ); + } + + if (!geneds || geneds.length === 0) { + return ( +
+ No geneds found for {selectedSchool}. If you would like to map geneds for your school, please fill in the feedback form in the sidebar! +
+ ); + } + + return ( + <> +
+ {data?.length} geneds found +
+
+ +
+ + ); + } useEffect(() => { - if (geneds && geneds.map) { + if (geneds && geneds?.map) { let mappedGeneds = geneds.map(gened => { const instructor = gened.fces[0]?.instructor; - if (instructor === "SIRBU, MARVIN") console.log(gened) - const filtered = filterFCEs(gened.fces, aggregationOptions); const aggregated = aggregateFCEs(filtered); return { @@ -55,24 +94,29 @@ const GenedsViewer = () => { selectedTags.some((tag) => gened.tags?.includes(tag)) ); } - if (query.length > 0) { + if (searchQuery.length > 0) { mappedGeneds = mappedGeneds.filter((gened) => - gened.name?.toLowerCase().includes(query.toLowerCase()) || - gened.instructor?.toLowerCase().includes(query.toLowerCase()) || - gened.tags?.some((tag) => tag.toLowerCase().includes(query.toLowerCase())) || - gened.desc?.toLowerCase().includes(query.toLowerCase()) || - gened.courseID?.toLowerCase().includes(query.toLowerCase()) + gened.name?.toLowerCase().includes(searchQuery.toLowerCase()) || + gened.instructor?.toLowerCase().includes(searchQuery.toLowerCase()) || + gened.tags?.some((tag) => tag.toLowerCase().includes(searchQuery.toLowerCase())) || + gened.desc?.toLowerCase().includes(searchQuery.toLowerCase()) || + gened.courseID?.toLowerCase().includes(searchQuery.toLowerCase()) ); } setData(mappedGeneds); setTags([...new Set(geneds.map(gened => gened.tags).flat().filter((tag) => tag !== undefined))]); } - }, [geneds, geneds.map, selectedTags, query]); + }, [geneds, geneds?.map, selectedTags, searchQuery]); return (
- dispatch(userSlice.actions.setSelectedSchool(payload))}> + { + dispatch(userSlice.actions.setSelectedSchool(payload)); + dispatch(userSlice.actions.setSelectedTags([])); + setSearchQuery(""); + }}> School @@ -190,13 +234,13 @@ const GenedsViewer = () => {
- setQuery(payload)}> + setSearchQuery(payload)}> Search setQuery(e.target.value)} + onChange={(e) => setSearchQuery(e.target.value)} placeholder="Search courses by ID, description, instructor, name, or tag..." /> @@ -212,11 +256,8 @@ const GenedsViewer = () => { form in the sidebar!
-
- {data.length} geneds found -
{ - !loggedIn && ( + !isSignedIn && (
) } - { - data.length === 0 ? ( -
- Loading... -
- ) : ( -
- -
- ) - } + {getGenedTable()} ); } diff --git a/apps/frontend/src/pages/_app.tsx b/apps/frontend/src/pages/_app.tsx index 571b0578..96d86edb 100644 --- a/apps/frontend/src/pages/_app.tsx +++ b/apps/frontend/src/pages/_app.tsx @@ -11,29 +11,36 @@ import Head from "next/head"; import { ClerkProvider } from "@clerk/nextjs"; import { PHProvider } from "~/app/providers"; import PostHogPageView from "~/app/PostHogPageView"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools' + +const queryClient = new QueryClient() export default function MyApp({ Component, pageProps }: AppProps) { return ( - - - - CMU Courses - - - - - - - - - - - + + + + + CMU Courses + + + + + + + + + + + + + ); } diff --git a/bun.lockb b/bun.lockb index 9d925645..7a74d19c 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index b9ce2d05..c84aa0cd 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ }, "devDependencies": { "@nx/next": "19.6.3", + "@tanstack/eslint-plugin-query": "^5.61.3", "@typescript-eslint/eslint-plugin": "^5.57.1", "@typescript-eslint/parser": "^5.57.1", "eslint-config-prettier": "^8.8.0", @@ -19,5 +20,9 @@ "workspaces": [ "apps/*", "packages/*" - ] + ], + "dependencies": { + "@tanstack/react-query": "^5.61.3", + "@tanstack/react-query-devtools": "^5.61.3" + } }