Skip to content

Commit 8a1e653

Browse files
Merge pull request #3 from AqViolet/master
Searchbar route setup
2 parents 5a0eaf2 + ef091e8 commit 8a1e653

File tree

3 files changed

+166
-19
lines changed

3 files changed

+166
-19
lines changed

pnpm-lock.yaml

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/app/catalogue/page.tsx

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"use client";
2+
3+
import { useSearchParams, useRouter } from "next/navigation";
4+
import { useEffect, useState } from "react";
5+
import axios, { AxiosError } from "axios";
6+
import Cryptr from "cryptr";
7+
8+
interface Paper {
9+
_id: string;
10+
exam: string;
11+
finalUrl: string;
12+
slot: string;
13+
subject: string;
14+
year: string;
15+
}
16+
17+
const cryptr = new Cryptr(process.env.NEXT_PUBLIC_CRYPTO_SECRET ?? "default_crypto_secret");
18+
19+
const Catalogue = () => {
20+
const router = useRouter();
21+
const searchParams = useSearchParams();
22+
const subject = searchParams.get('subject');
23+
const [papers, setPapers] = useState<Paper[]>([]);
24+
const [error, setError] = useState<string | null>(null);
25+
const [loading, setLoading] = useState<boolean>(false);
26+
27+
useEffect(() => {
28+
if (subject) {
29+
const fetchPapers = async () => {
30+
setLoading(true);
31+
32+
try {
33+
const papersResponse = await axios.get("http://localhost:3000/api/papers", {
34+
params: { subject },
35+
});
36+
37+
const { res: encryptedPapersResponse } = papersResponse.data;
38+
const decryptedPapersResponse = cryptr.decrypt(encryptedPapersResponse);
39+
// console.log("Decrypted Papers Response:", decryptedPapersResponse);
40+
41+
const papersData: Paper[] = JSON.parse(decryptedPapersResponse).papers;
42+
setPapers(papersData);
43+
} catch (error) {
44+
45+
if (axios.isAxiosError(error)) {
46+
const axiosError = error as AxiosError<{ message?: string }>;
47+
const errorMessage = axiosError.response?.data?.message || "Error fetching papers";
48+
setError(errorMessage);
49+
50+
} else {
51+
setError("Error fetching papers");
52+
}
53+
54+
} finally {
55+
setLoading(false);
56+
}
57+
};
58+
59+
fetchPapers();
60+
}
61+
}, [subject]);
62+
63+
return (
64+
<div className="min-h-screen bg-gray-50 p-8">
65+
66+
<button onClick={() => router.push('/')} className="mb-4 px-4 py-2 bg-blue-500 text-white rounded-md">
67+
Back to Search
68+
</button>
69+
70+
<h1 className="text-2xl font-bold mb-4">Papers for {subject}</h1>
71+
{error && <p className="text-red-500">{error}</p>}
72+
73+
{loading ? (
74+
<p>Loading papers...</p>
75+
) : (
76+
papers.length > 0 ? (
77+
<div className="grid grid-cols-1 gap-4">
78+
79+
{papers.map((paper) => (
80+
<div key={paper._id} className="border rounded-md p-4 shadow-md bg-white">
81+
82+
<h3 className="text-xl font-bold">Exam: {paper.exam}</h3>
83+
<p>Slot: {paper.slot}</p>
84+
<p>Subject: {paper.subject}</p>
85+
<p>Year: {paper.year}</p>
86+
87+
<a href={paper.finalUrl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:underline">
88+
View Paper
89+
</a>
90+
91+
</div>
92+
))}
93+
</div>
94+
) : (
95+
<p>No papers available for this subject.</p>
96+
)
97+
)}
98+
</div>
99+
);
100+
};
101+
102+
export default Catalogue;

src/app/components/searchbar.tsx

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,79 @@
11
"use client";
2+
23
import { useState } from "react";
4+
import axios from "axios";
35
import { Search } from "lucide-react";
6+
import Cryptr from "cryptr";
7+
import { useRouter } from "next/navigation";
8+
9+
const cryptr = new Cryptr(
10+
process.env.NEXT_PUBLIC_CRYPTO_SECRET ?? "default_crypto_secret"
11+
);
412

513
const SearchBar = () => {
14+
const router = useRouter();
615
const [searchText, setSearchText] = useState("");
16+
const [suggestions, setSuggestions] = useState<string[]>([]);
17+
const [error, setError] = useState<string | null>(null);
18+
19+
const handleSearchChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
20+
const text = e.target.value;
21+
setSearchText(text);
22+
23+
if (text.length > 1) {
24+
try {
25+
const searchResponse = await axios.get("http://localhost:3000/api/search", {
26+
params: { text },
27+
});
28+
29+
const { res: encryptedSearchResponse } = searchResponse.data;
30+
const decryptedSearchResponse = cryptr.decrypt(encryptedSearchResponse);
31+
// console.log("Decrypted Search Response:", decryptedSearchResponse);
32+
33+
const { subjects } = JSON.parse(decryptedSearchResponse);
34+
const suggestionList = subjects.map((subjectObj: { subject: string }) => subjectObj.subject);
35+
setSuggestions(suggestionList);
36+
} catch (error) {
37+
setError("Error fetching suggestions");
38+
}
39+
} else {
40+
setSuggestions([]);
41+
}
42+
};
743

8-
const handleSearch = (event: React.FormEvent) => {
9-
event.preventDefault();
10-
console.log("Searching for:", searchText);
44+
const handleSelectSuggestion = async (suggestion: string) => {
45+
setSearchText(suggestion);
46+
setSuggestions([]);
47+
router.push(`/catalogue?subject=${encodeURIComponent(suggestion)}`);
1148
};
1249

1350
return (
14-
<div className="flex min-h-screen items-center justify-center bg-gray-50">
15-
<form onSubmit={handleSearch} className="w-full max-w-md">
51+
<div className="flex min-h-screen items-center justify-center bg-gray-50 flex-col">
52+
<form className="w-full max-w-md">
1653
<div className="relative">
17-
<input
18-
type="text"
19-
value={searchText}
20-
onChange={(e) => setSearchText(e.target.value)}
21-
placeholder="Search..."
22-
className="w-full rounded-md border border-gray-300 px-4 py-2 pr-10 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"
23-
/>
24-
<button
25-
type="submit"
26-
className="absolute inset-y-0 right-0 flex items-center pr-3"
27-
>
54+
55+
<input type="text" value={searchText} onChange={handleSearchChange}
56+
placeholder="Search..." className="w-full rounded-md border border-gray-300 px-4 py-2 pr-10 shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500"/>
57+
58+
<button type="submit" className="absolute inset-y-0 right-0 flex items-center pr-3">
2859
<Search className="h-5 w-5 text-gray-400" />
2960
</button>
61+
3062
</div>
63+
{suggestions.length > 0 && (
64+
<ul className="absolute z-10 w-full bg-white border border-gray-300 rounded-md mt-2">
65+
{suggestions.map((suggestion, index) => (
66+
67+
<li key={index} onClick={() => handleSelectSuggestion(suggestion)} className="cursor-pointer p-2 hover:bg-gray-100">
68+
{suggestion}
69+
</li>
70+
71+
))}
72+
</ul>
73+
)}
3174
</form>
75+
76+
{error && <p className="mt-4 text-red">{error}</p>}
3277
</div>
3378
);
3479
};

0 commit comments

Comments
 (0)