Skip to content

Commit 635b9e8

Browse files
authored
Merge pull request #124 from Abh1noob/search-optimization
Search Optimization
2 parents 8d92e7f + 99812e6 commit 635b9e8

File tree

9 files changed

+199
-219
lines changed

9 files changed

+199
-219
lines changed

src/app/api/papers/route.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { NextResponse, type NextRequest } from "next/server";
22
import { connectToDatabase } from "@/lib/mongoose";
33
import Paper from "@/db/papers";
4-
import { type IPaper, } from "@/interface";
4+
import { type IPaper } from "@/interface";
55

66
export const dynamic = "force-dynamic";
77

@@ -21,11 +21,11 @@ export async function GET(req: NextRequest) {
2121
{ status: 400 },
2222
);
2323
}
24-
console.log((await Paper.find()).map((paper)=> paper.campus))
24+
2525
const papers: IPaper[] = await Paper.find({
2626
subject: { $regex: new RegExp(`${escapedSubject}`, "i") },
2727
});
28-
console.log(papers[0]?.campus)
28+
2929
if (papers.length === 0) {
3030
return NextResponse.json(
3131
{ message: "No papers found for the specified subject" },
@@ -36,17 +36,28 @@ export async function GET(req: NextRequest) {
3636
const uniqueYears = Array.from(new Set(papers.map((paper) => paper.year)));
3737
const uniqueSlots = Array.from(new Set(papers.map((paper) => paper.slot)));
3838
const uniqueExams = Array.from(new Set(papers.map((paper) => paper.exam)));
39-
const uniqueCampuses = Array.from(new Set(papers.map((paper) => paper.campus)));
40-
const uniqueSemesters = Array.from(new Set(papers.map((paper) => paper.semester)));
39+
const uniqueCampuses = Array.from(
40+
new Set(papers.map((paper) => paper.campus)),
41+
);
42+
const uniqueSemesters = Array.from(
43+
new Set(papers.map((paper) => paper.semester)),
44+
);
4145

4246
return NextResponse.json(
43-
{ papers, uniqueYears, uniqueSlots, uniqueExams, uniqueCampuses, uniqueSemesters },
44-
{ status: 200 }
47+
{
48+
papers,
49+
uniqueYears,
50+
uniqueSlots,
51+
uniqueExams,
52+
uniqueCampuses,
53+
uniqueSemesters,
54+
},
55+
{ status: 200 },
4556
);
4657
} catch (error) {
4758
return NextResponse.json(
4859
{ message: "Failed to fetch papers", error },
49-
{ status: 500 }
60+
{ status: 500 },
5061
);
5162
}
5263
}

src/app/api/search/route.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/app/api/selected-papers/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export async function GET() {
1010

1111
const selectedPapers = await Paper.find({ isSelected: true }).limit(4);
1212

13-
console.log("Selected papers:", selectedPapers);
1413
if (selectedPapers.length === 0) {
1514
return NextResponse.json(
1615
{

src/app/catalogue/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
"use client";
2-
32
import CatalogueContent from "@/components/CatalogueContent";
43
import { Suspense } from "react";
54
import Navbar from "@/components/Navbar";

src/app/page.tsx

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import SearchBar from "@/components/searchbar";
21
import Navbar from "@/components/Navbar";
32
import StoredPapers from "@/components/StoredPapers";
43
import Footer from "@/components/Footer";
4+
import SearchBar from "@/components/Searchbar/searchbar";
55

66
const HomePage = () => {
77
return (
@@ -10,45 +10,47 @@ const HomePage = () => {
1010
<Navbar />
1111
</div>
1212
<div className="mt-2 flex flex-grow flex-col items-center justify-center gap-y-10">
13-
1413
<div className="w-full max-w-2xl space-y-10 text-center">
15-
16-
<h1 className="phonk text-2xl md:text-3xl mx-auto">
14+
<h1 className="phonk mx-auto text-2xl md:text-3xl">
1715
Built by Students for Students
1816
</h1>
19-
20-
<p className="text-md font-semibold font-sans w-[90%] mx-auto md:w-full">
21-
Prepare to excel in your CATs and FATs with CodeChef-VIT&apos;s dedicated
22-
repository of past exam papers. Access key resources to review
23-
concepts, tackle challenging questions, and familiarize yourself
24-
with exam patterns. Boost your confidence, sharpen your strategy,
25-
and get ready to ace your exams!
17+
18+
<p className="text-md mx-auto w-[90%] font-sans font-semibold md:w-full">
19+
Prepare to excel in your CATs and FATs with CodeChef-VIT&apos;s
20+
dedicated repository of past exam papers. Access key resources to
21+
review concepts, tackle challenging questions, and familiarize
22+
yourself with exam patterns. Boost your confidence, sharpen your
23+
strategy, and get ready to ace your exams!
2624
</p>
27-
25+
2826
<div className="flex flex-wrap justify-center gap-4 text-sm font-bold">
29-
{["NO SIGN UP REQUIRED", "FILTERED SEARCH", "FLEXIBLE DOWNLOAD"].map((text) => (
30-
<div key={text} className="p-[2px] bg-gradient-to-r from-[#562EE7] to-[#bd21b4] rounded-full">
31-
<div className="rounded-full bg-white dark:bg-black px-6 py-3 tracking-wider text-black dark:text-white font-sans">
32-
{text}
27+
{[
28+
"NO SIGN UP REQUIRED",
29+
"FILTERED SEARCH",
30+
"FLEXIBLE DOWNLOAD",
31+
].map((text) => (
32+
<div
33+
key={text}
34+
className="rounded-full bg-gradient-to-r from-[#562EE7] to-[#bd21b4] p-[2px]"
35+
>
36+
<div className="rounded-full bg-white px-6 py-3 font-sans tracking-wider text-black dark:bg-black dark:text-white">
37+
{text}
38+
</div>
3339
</div>
34-
</div>
3540
))}
3641
</div>
37-
3842
</div>
39-
43+
4044
<div className="z-20 w-full max-w-xl">
4145
<SearchBar />
4246
</div>
43-
47+
4448
<div className="max-3xl w-full">
4549
<StoredPapers />
4650
</div>
47-
4851
</div>
49-
52+
5053
<Footer />
51-
5254
</div>
5355
);
5456
};

src/components/CatalogueContent.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import { useSearchParams } from "next/navigation";
24
import { useEffect, useState } from "react";
35
import axios, { type AxiosError } from "axios";
@@ -7,9 +9,10 @@ import { FilterDialog } from "@/components/FilterDialog";
79
import Card from "./Card";
810
import { extractBracketContent } from "@/util/utils";
911
import { useRouter } from "next/navigation";
10-
import SearchBar from "./searchbar";
12+
import SearchBarChild from "./Searchbar/searchbar-child";
1113
import Loader from "./ui/loader";
1214
import { campuses, semesters } from "./select_options";
15+
import SearchBar from "./Searchbar/searchbar";
1316

1417
const CatalogueContent = () => {
1518
const searchParams = useSearchParams();
@@ -163,8 +166,8 @@ const CatalogueContent = () => {
163166

164167
return (
165168
<div className="min-h-screen px-2 md:p-8">
166-
<div className="mb-10 flex w-full flex-row items-center md:justify-between md:gap-10">
167-
<div className=" w-[120%] md:w-[576px]">
169+
<div className="mb-10 flex w-full flex-row items-center md:justify-between md:gap-10">
170+
<div className="w-[120%] md:w-[576px]">
168171
<SearchBar />
169172
</div>
170173
<div className="flex gap-8">
@@ -181,18 +184,26 @@ const CatalogueContent = () => {
181184
onApplyFilters={handleApplyFilters}
182185
/>
183186
)}{" "}
184-
<div className=" hidden items-center justify-center gap-2 md:flex md:justify-end 2xl:mr-4">
185-
<Button variant="outline" onClick={handleSelectAll} className="font-sans font-semibold border-2 border-black dark:border-[#434dba] hover:bg-slate-800 hover:text-white dark:hover:bg-slate-900 dark:hover:border-white">
187+
<div className="hidden items-center justify-center gap-2 md:flex md:justify-end 2xl:mr-4">
188+
<Button
189+
variant="outline"
190+
onClick={handleSelectAll}
191+
className="border-2 border-black font-sans font-semibold hover:bg-slate-800 hover:text-white dark:border-[#434dba] dark:hover:border-white dark:hover:bg-slate-900"
192+
>
186193
Select All
187194
</Button>
188-
<Button variant="outline" onClick={handleDeselectAll} className="font-sans font-semibold border-2 border-black dark:border-[#434dba] hover:bg-slate-800 hover:text-white dark:hover:bg-slate-900 dark:hover:border-white">
195+
<Button
196+
variant="outline"
197+
onClick={handleDeselectAll}
198+
className="border-2 border-black font-sans font-semibold hover:bg-slate-800 hover:text-white dark:border-[#434dba] dark:hover:border-white dark:hover:bg-slate-900"
199+
>
189200
Deselect All
190201
</Button>
191202
<Button
192203
variant="outline"
193204
onClick={handleDownloadAll}
194205
disabled={selectedPapers.length === 0}
195-
className="font-sans font-semibold border-2 border-black dark:border-[#434dba] hover:bg-slate-800 hover:text-white dark:hover:bg-slate-900 dark:hover:border-white"
206+
className="border-2 border-black font-sans font-semibold hover:bg-slate-800 hover:text-white dark:border-[#434dba] dark:hover:border-white dark:hover:bg-slate-900"
196207
>
197208
Download All ({selectedPapers.length})
198209
</Button>
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use client";
2+
3+
import { useState, useRef, useEffect } from "react";
4+
import { Search } from "lucide-react";
5+
import { useRouter } from "next/navigation";
6+
import { Input } from "@/components/ui/input";
7+
8+
function SearchBarChild({ initialSubjects }: { initialSubjects: string[] }) {
9+
const router = useRouter();
10+
const [searchText, setSearchText] = useState("");
11+
const [suggestions, setSuggestions] = useState<string[]>([]);
12+
const [isSearching, setIsSearching] = useState(false);
13+
const [subjects] = useState<string[]>(initialSubjects);
14+
const suggestionsRef = useRef<HTMLUListElement | null>(null);
15+
const inputRef = useRef<HTMLInputElement | null>(null);
16+
17+
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
18+
const text = e.target.value;
19+
setSearchText(text);
20+
setIsSearching(true);
21+
22+
if (text.length > 1 && subjects.length > 0) {
23+
const filteredSuggestions = subjects.filter((subject) =>
24+
subject.toLowerCase().includes(text.toLowerCase()),
25+
);
26+
setSuggestions(filteredSuggestions);
27+
} else {
28+
setSuggestions([]);
29+
}
30+
};
31+
32+
const handleSelectSuggestion = (suggestion: string) => {
33+
setSearchText(suggestion);
34+
setSuggestions([]);
35+
setIsSearching(false);
36+
router.push(`/catalogue?subject=${encodeURIComponent(suggestion)}`);
37+
};
38+
39+
const handleClickOutside = (event: MouseEvent) => {
40+
if (
41+
suggestionsRef.current &&
42+
!suggestionsRef.current.contains(event.target as Node) &&
43+
!inputRef.current?.contains(event.target as Node)
44+
) {
45+
setSuggestions([]);
46+
setIsSearching(false);
47+
}
48+
};
49+
50+
useEffect(() => {
51+
document.addEventListener("mousedown", handleClickOutside);
52+
return () => {
53+
document.removeEventListener("mousedown", handleClickOutside);
54+
};
55+
}, []);
56+
57+
return (
58+
<div className="mx-4 md:mx-0">
59+
<form
60+
className="w-full max-w-xl"
61+
onSubmit={(e) => {
62+
e.preventDefault();
63+
if (searchText) {
64+
setIsSearching(false);
65+
router.push(`/catalogue?subject=${encodeURIComponent(searchText)}`);
66+
}
67+
}}
68+
>
69+
<div className="relative">
70+
<Input
71+
ref={inputRef}
72+
type="text"
73+
value={searchText}
74+
onChange={handleSearchChange}
75+
onFocus={() => setIsSearching(true)}
76+
placeholder="Search by subject..."
77+
className="text-md w-full rounded-full border bg-[#434dba] px-4 py-6 pr-10 font-sans tracking-wider text-white shadow-sm placeholder:text-white focus:outline-none focus:ring-2"
78+
/>
79+
<button
80+
type="submit"
81+
className="absolute inset-y-0 right-0 flex items-center pr-3"
82+
>
83+
<Search className="h-5 w-5 text-white" />
84+
</button>
85+
{isSearching &&
86+
(suggestions.length > 0 ||
87+
(searchText.length > 1 && subjects.length > 0)) && (
88+
<ul
89+
ref={suggestionsRef}
90+
className="absolute z-20 mx-0.5 mt-2 w-full max-w-xl rounded-md border border-[#434dba] bg-white text-center shadow-lg dark:bg-[#030712] md:mx-0"
91+
>
92+
{suggestions.length > 0 ? (
93+
suggestions.map((suggestion, index) => (
94+
<li
95+
key={index}
96+
onClick={() => handleSelectSuggestion(suggestion)}
97+
className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
98+
>
99+
{suggestion}
100+
</li>
101+
))
102+
) : (
103+
<li className="p-2 text-gray-500 dark:text-gray-400">
104+
No subjects found
105+
</li>
106+
)}
107+
</ul>
108+
)}
109+
</div>
110+
</form>
111+
</div>
112+
);
113+
}
114+
115+
export default SearchBarChild;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"use server";
2+
import axios from "axios";
3+
import { type ICourses } from "@/interface";
4+
import SearchBarChild from "./searchbar-child";
5+
6+
async function fetchSubjects() {
7+
try {
8+
const response = await axios.get<ICourses[]>(
9+
`${process.env.SERVER_URL}/api/course-list`,
10+
);
11+
return response.data.map((course) => course.name);
12+
} catch (err) {
13+
console.error("Error fetching subjects:", err);
14+
return [];
15+
}
16+
}
17+
18+
export default async function SearchBar() {
19+
const subjects = await fetchSubjects();
20+
21+
return <SearchBarChild initialSubjects={subjects} />;
22+
}

0 commit comments

Comments
 (0)