diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1aa9c74..0dc78be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3385,7 +3385,7 @@ snapshots: '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/compat-data@7.25.4': {} @@ -3470,7 +3470,7 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 chalk: 2.4.2 js-tokens: 4.0.0 - picocolors: 1.0.1 + picocolors: 1.1.0 '@babel/parser@7.25.6': dependencies: @@ -6878,7 +6878,7 @@ snapshots: webpack@5.95.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@webassemblyjs/ast': 1.12.1 '@webassemblyjs/wasm-edit': 1.12.1 '@webassemblyjs/wasm-parser': 1.12.1 diff --git a/src/app/catalogue/page.tsx b/src/app/catalogue/page.tsx new file mode 100644 index 0000000..665ebe5 --- /dev/null +++ b/src/app/catalogue/page.tsx @@ -0,0 +1,102 @@ +"use client"; + +import { useSearchParams, useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; +import axios, { AxiosError } from "axios"; +import Cryptr from "cryptr"; + +interface Paper { + _id: string; + exam: string; + finalUrl: string; + slot: string; + subject: string; + year: string; +} + +const cryptr = new Cryptr(process.env.NEXT_PUBLIC_CRYPTO_SECRET ?? "default_crypto_secret"); + +const Catalogue = () => { + const router = useRouter(); + const searchParams = useSearchParams(); + const subject = searchParams.get('subject'); + const [papers, setPapers] = useState([]); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (subject) { + const fetchPapers = async () => { + setLoading(true); + + try { + const papersResponse = await axios.get("http://localhost:3000/api/papers", { + params: { subject }, + }); + + const { res: encryptedPapersResponse } = papersResponse.data; + const decryptedPapersResponse = cryptr.decrypt(encryptedPapersResponse); + // console.log("Decrypted Papers Response:", decryptedPapersResponse); + + const papersData: Paper[] = JSON.parse(decryptedPapersResponse).papers; + setPapers(papersData); + } catch (error) { + + if (axios.isAxiosError(error)) { + const axiosError = error as AxiosError<{ message?: string }>; + const errorMessage = axiosError.response?.data?.message || "Error fetching papers"; + setError(errorMessage); + + } else { + setError("Error fetching papers"); + } + + } finally { + setLoading(false); + } + }; + + fetchPapers(); + } + }, [subject]); + + return ( +
+ + + +

Papers for {subject}

+ {error &&

{error}

} + + {loading ? ( +

Loading papers...

+ ) : ( + papers.length > 0 ? ( +
+ + {papers.map((paper) => ( +
+ +

Exam: {paper.exam}

+

Slot: {paper.slot}

+

Subject: {paper.subject}

+

Year: {paper.year}

+ + + View Paper + + +
+ ))} +
+ ) : ( +

No papers available for this subject.

+ ) + )} +
+ ); +}; + +export default Catalogue; diff --git a/src/app/components/searchbar.tsx b/src/app/components/searchbar.tsx index cffc5f6..aa1fcf2 100644 --- a/src/app/components/searchbar.tsx +++ b/src/app/components/searchbar.tsx @@ -1,34 +1,79 @@ "use client"; + import { useState } from "react"; +import axios from "axios"; import { Search } from "lucide-react"; +import Cryptr from "cryptr"; +import { useRouter } from "next/navigation"; + +const cryptr = new Cryptr( + process.env.NEXT_PUBLIC_CRYPTO_SECRET ?? "default_crypto_secret" +); const SearchBar = () => { + const router = useRouter(); const [searchText, setSearchText] = useState(""); + const [suggestions, setSuggestions] = useState([]); + const [error, setError] = useState(null); + + const handleSearchChange = async (e: React.ChangeEvent) => { + const text = e.target.value; + setSearchText(text); + + if (text.length > 1) { + try { + const searchResponse = await axios.get("http://localhost:3000/api/search", { + params: { text }, + }); + + const { res: encryptedSearchResponse } = searchResponse.data; + const decryptedSearchResponse = cryptr.decrypt(encryptedSearchResponse); + // console.log("Decrypted Search Response:", decryptedSearchResponse); + + const { subjects } = JSON.parse(decryptedSearchResponse); + const suggestionList = subjects.map((subjectObj: { subject: string }) => subjectObj.subject); + setSuggestions(suggestionList); + } catch (error) { + setError("Error fetching suggestions"); + } + } else { + setSuggestions([]); + } + }; - const handleSearch = (event: React.FormEvent) => { - event.preventDefault(); - console.log("Searching for:", searchText); + const handleSelectSuggestion = async (suggestion: string) => { + setSearchText(suggestion); + setSuggestions([]); + router.push(`/catalogue?subject=${encodeURIComponent(suggestion)}`); }; return ( -
-
+
+
- setSearchText(e.target.value)} - 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" - /> - +
+ {suggestions.length > 0 && ( +
    + {suggestions.map((suggestion, index) => ( + +
  • handleSelectSuggestion(suggestion)} className="cursor-pointer p-2 hover:bg-gray-100"> + {suggestion} +
  • + + ))} +
+ )} + + {error &&

{error}

}
); };