diff --git a/frontend/src/SearchResults.tsx b/frontend/src/SearchResults.tsx new file mode 100644 index 0000000..2b60ee9 --- /dev/null +++ b/frontend/src/SearchResults.tsx @@ -0,0 +1,129 @@ +import { queryOptions, useQuery } from "@tanstack/react-query"; +import { ErrorComponent, Route, notFound } from "@tanstack/react-router"; +import axios, { AxiosError } from "axios"; +import { z } from "zod"; +import { timetableWithSectionsType } from "../../lib/src"; +import authenticatedRoute from "./AuthenticatedRoute"; +import { ToastAction } from "./components/ui/toast"; +import { useToast } from "./components/ui/use-toast"; +import { router } from "./main"; + +const fetchSearchDetails = async ( + query: string, +): Promise[]> => { + const response = await axios.get[]>( + `/api/timetable/search?query=${query}`, + { + headers: { + "Content-Type": "application/json", + }, + }, + ); + return response.data; +}; + +const searchQueryOptions = (query: string) => + queryOptions({ + queryKey: ["search_timetables", query], + queryFn: () => fetchSearchDetails(query), + }); + +const searchRoute = new Route({ + getParentRoute: () => authenticatedRoute, + path: "search/$query", + component: SearchResults, + loader: ({ context: { queryClient }, params }) => + queryClient + .ensureQueryData(searchQueryOptions(params.query)) + .catch((error: Error) => { + if ( + error instanceof AxiosError && + error.response && + error.response.status === 401 + ) { + router.navigate({ + to: "/login", + }); + } + + throw error; + }), + errorComponent: ({ error }) => { + const { toast } = useToast(); + + if (error instanceof AxiosError) { + if (error.response) { + switch (error.response.status) { + case 404: + toast({ + title: "Error", + description: + "message" in error.response.data + ? error.response.data.message + : "API returned 404", + variant: "destructive", + action: ( + + + Report + + + ), + }); + break; + case 500: + toast({ + title: "Server Error", + description: + "message" in error.response.data + ? error.response.data.message + : "API returned 500", + variant: "destructive", + action: ( + + + Report + + + ), + }); + break; + + default: + toast({ + title: "Unknown Error", + description: + "message" in error.response.data + ? error.response.data.message + : `API returned ${error.response.status}`, + variant: "destructive", + action: ( + + + Report + + + ), + }); + } + } else { + return ; + } + } + }, +}); + +function SearchResults() { + const { query } = searchRoute.useParams(); + // @ts-ignore Suppress unused variable warning, needs to be removed when the page is finished + const searchQueryResult = useQuery(searchQueryOptions(query)); + return ( +
+

+ Search Results +

+
+ ); +} + +export default searchRoute; diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx new file mode 100644 index 0000000..5030923 --- /dev/null +++ b/frontend/src/components/SearchBar.tsx @@ -0,0 +1,71 @@ +import { router } from "@/main"; +import { ListFilter, Search } from "lucide-react"; +import { useRef } from "react"; +import { Button } from "./ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "./ui/dropdown-menu"; +import { Input } from "./ui/input"; +import { useToast } from "./ui/use-toast"; + +const SearchBar = () => { + const { toast } = useToast(); + const searchRef = useRef(null); + const handleSearch = async (query: string | undefined) => { + if (query === undefined || query.length < 2) { + toast({ + title: "Error", + variant: "destructive", + description: "Search query has to be atleast 2 characters long", + }); + return; + } + router.navigate({ + to: "/search/$query", + params: { query }, + }); + }; + return ( +
+
+ + handleSearch(searchRef.current?.value)} + /> +
+ + + + + + Filter by + + Course + Name + Archived + + +
+ ); +}; + +export default SearchBar; diff --git a/frontend/src/components/announcements.tsx b/frontend/src/components/announcements.tsx index 86b4d0f..a071d57 100644 --- a/frontend/src/components/announcements.tsx +++ b/frontend/src/components/announcements.tsx @@ -66,7 +66,7 @@ function Announcements() { return ( - diff --git a/frontend/src/components/navbar.tsx b/frontend/src/components/navbar.tsx index 0927b68..1b654bb 100644 --- a/frontend/src/components/navbar.tsx +++ b/frontend/src/components/navbar.tsx @@ -16,13 +16,15 @@ import { } from "@tanstack/react-query"; import { Link, useRouter } from "@tanstack/react-router"; import axios, { AxiosError } from "axios"; -import { BookUp, Info, LogOut, Pencil, Plus } from "lucide-react"; +import { BookUp, Info, LogOut, Pencil, Plus, Search } from "lucide-react"; import { useCookies } from "react-cookie"; import { z } from "zod"; import { userWithTimetablesType } from "../../../lib/src/index"; import { router } from "../main"; +import SearchBar from "./SearchBar"; import Announcements from "./announcements"; import { ModeToggle } from "./mode-toggle"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; const fetchUserDetails = async (): Promise< z.infer @@ -162,7 +164,7 @@ export function NavBar() { {!isEditPage && (