+
{pathname === "/upload" ? "Search Papers" : "Upload Papers"}
diff --git a/src/components/PapersCarousel.tsx b/src/components/PapersCarousel.tsx
index 38a73e5..1c2a4ad 100644
--- a/src/components/PapersCarousel.tsx
+++ b/src/components/PapersCarousel.tsx
@@ -14,6 +14,7 @@ import {
import Autoplay from "embla-carousel-autoplay";
import { chunkArray } from "@/util/utils";
import { Skeleton } from "@/components/ui/skeleton";
+import SkeletonPaperCard from "@/components/SkeletonPaperCard";
function PapersCarousel() {
const [displayPapers, setDisplayPapers] = useState([]);
@@ -78,23 +79,7 @@ function PapersCarousel() {
chunkSize === 4 ? "grid-cols-2 grid-rows-2" : "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
- {Array.from({ length: chunkSize }).map((_, idx) => (
-
- ))}
+
) : (
chunkedPapers.map((paperGroup, index) => {
diff --git a/src/components/PinnedPapersCarousel.tsx b/src/components/PinnedPapersCarousel.tsx
index 449cd41..bd475bd 100644
--- a/src/components/PinnedPapersCarousel.tsx
+++ b/src/components/PinnedPapersCarousel.tsx
@@ -17,6 +17,7 @@ import AddPapers from "./AddPapers";
import Autoplay from "embla-carousel-autoplay";
import { chunkArray } from "@/util/utils";
import { StoredSubjects } from "@/interface";
+import SkeletonPaperCard from "./SkeletonPaperCard";
function PinnedPapersCarousel({
carouselType = "upcoming",
@@ -54,15 +55,11 @@ function PinnedPapersCarousel({
localStorage.getItem("userSubjects") ?? "[]",
) as StoredSubjects;
- console.log("Fetching papers for subjects:", storedSubjects);
-
const response = await axios.post<{ subject: string; slots: string[] }[]>(
"/api/user-papers",
storedSubjects,
);
- console.log("Fetched papers:", response.data);
-
const fetchedPapers = response.data;
const fetchedSubjectsSet = new Set(
@@ -131,23 +128,7 @@ function PinnedPapersCarousel({
chunkSize === 4 ? "grid-cols-2 grid-rows-2" : "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
- {Array.from({ length: chunkSize }).map((_, idx) => (
-
- ))}
+
) : (
chunkedPapers.map((paperGroup, index) => {
diff --git a/src/components/Searchbar/searchbar-child.tsx b/src/components/Searchbar/searchbar-child.tsx
index 8d0ed36..b3a83f2 100644
--- a/src/components/Searchbar/searchbar-child.tsx
+++ b/src/components/Searchbar/searchbar-child.tsx
@@ -145,7 +145,7 @@ function SearchBarChild({
{suggestion}
diff --git a/src/components/SkeletonPaperCard.tsx b/src/components/SkeletonPaperCard.tsx
new file mode 100644
index 0000000..bcc50fc
--- /dev/null
+++ b/src/components/SkeletonPaperCard.tsx
@@ -0,0 +1,29 @@
+import { Skeleton } from "@/components/ui/skeleton";
+
+interface SkeletonPaperCardProps {
+ length?: number;
+}
+
+export default function SkeletonPaperCard({ length = 4 }: SkeletonPaperCardProps) {
+ return (
+ <>
+ {Array.from({ length }).map((_, idx) => (
+
+ ))}
+ >
+ );
+}
diff --git a/src/components/screens/PapersPage.tsx b/src/components/screens/PapersPage.tsx
deleted file mode 100644
index 60702e4..0000000
--- a/src/components/screens/PapersPage.tsx
+++ /dev/null
@@ -1,296 +0,0 @@
-"use client";
-
-import { useEffect, useState, useRef, useMemo } from "react";
-import { Button } from "@/components/ui/button";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { exams, slots, years } from "@/components/select_options";
-import { Input } from "@/components/ui/input";
-import axios from "axios";
-import Fuse from "fuse.js";
-import { ArrowRight } from "lucide-react";
-import Image from "next/image";
-import { IUpcomingPaper } from "@/interface";
-import { Skeleton } from "../ui/skeleton";
-import { CarouselItem } from "@/components/ui/carousel";
-import UpcomingPaper from "../UpcomingPaper";
-
-type Course = {
- name?: string | null;
- courseName?: string | null;
- title?: string | null;
-};
-
-export default function PapersPage() {
- const [subjects, setSubjects] = useState
([]);
- const [searchText, setSearchText] = useState("");
- const [suggestions, setSuggestions] = useState([]);
- const [selectedSubject, setSelectedSubject] = useState(null);
- const [selectedExam, setSelectedExam] = useState(null);
- const [selectedSlot, setSelectedSlot] = useState(null);
- const [selectedYear, setSelectedYear] = useState(null);
- const suggestionsRef = useRef(null);
- const [displayPapers, setDisplayPapers] = useState([]);
- const [isLoading, setIsLoading] = useState(true);
-
- useEffect(() => {
- async function fetchSubjects() {
- try {
- const response = await axios.get(`/api/course-list`);
- const courses: Course[] = response.data;
- const names = courses
- .map((course) => course.name ?? course.courseName ?? course.title)
- .filter(Boolean) as string[];
-
- setSubjects(names);
- } catch (err) {
- console.error("Error fetching subjects:", err);
- }
- }
- void fetchSubjects();
- }, []);
-
- useEffect(() => {
- async function fetchPapers() {
- try {
- setIsLoading(true);
- const response = await axios.get(
- "/api/upcoming-papers",
- );
-
- const randomPapers = [...response.data]
- .sort(() => Math.random() - 0.5)
- .slice(0, 4);
-
- setDisplayPapers(randomPapers);
- } catch (error) {
- console.error("Failed to fetch papers:", error);
- } finally {
- setIsLoading(false);
- }
- }
-
- void fetchPapers();
- }, []);
-
- const fuse = useMemo(
- () => new Fuse(subjects, { includeScore: true, threshold: 0.3 }),
- [subjects],
- );
-
- useEffect(() => {
- if (!searchText.trim()) {
- setSuggestions([]);
- return;
- }
-
- if (selectedSubject && searchText === selectedSubject) {
- setSuggestions([]);
- return;
- }
-
- const results = fuse.search(searchText);
- setSuggestions(results.map((r) => r.item).slice(0, 10));
- }, [searchText, fuse, selectedSubject]);
-
- const handleSelectSubject = (subject: string) => {
- setSelectedSubject(subject);
- setSearchText(subject);
- setSuggestions([]);
- setSelectedExam(null);
- setSelectedSlot(null);
- setSelectedYear(null);
- };
-
- useEffect(() => {
- function handleClickOutside(event: MouseEvent) {
- if (
- suggestionsRef.current &&
- !suggestionsRef.current.contains(event.target as Node)
- ) {
- setSuggestions([]);
- }
- }
- document.addEventListener("mousedown", handleClickOutside);
- return () => document.removeEventListener("mousedown", handleClickOutside);
- }, []);
-
- const handleSubmit = async () => {
- if (!selectedSubject || !selectedExam || !selectedSlot || !selectedYear) {
- alert("⚠️ Please fill all fields before submitting.");
- return;
- }
-
- try {
- await axios.post("/api/request", {
- subject: selectedSubject,
- exam: selectedExam,
- slot: selectedSlot,
- year: selectedYear,
- });
-
- alert("✅ Your paper request was submitted successfully 🎉");
-
- setSearchText("");
- setSelectedSubject(null);
- setSelectedExam(null);
- setSelectedSlot(null);
- setSelectedYear(null);
- } catch (error) {
- console.error("Error submitting request:", error);
- alert("❌ Failed to submit your request. Please try again later.");
- }
- };
-
- return (
-
-
-
-
- Specific Paper Request
-
-
-
-
setSearchText(e.target.value)}
- placeholder="Search by subject..."
- className={`text-md rounded-lg bg-[#B2B8FF] px-4 py-6 pr-10 font-play tracking-wider text-black shadow-sm ring-0 placeholder:text-black focus:outline-none focus:ring-0 dark:bg-[#7480FF66] dark:text-white placeholder:dark:text-white ${suggestions.length > 0 ? "rounded-b-none" : ""}`}
- />
-
-
-
-
-
- {suggestions.length > 0 && (
-
- {suggestions.map((s, idx) => (
- handleSelectSubject(s)}
- className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
- >
- {s}
-
- ))}
-
- )}
-
-
-
-
-
-
-
-
- {exams.map((exam) => (
-
- {exam}
-
- ))}
-
-
-
-
-
-
-
- {slots.map((slot) => (
-
- {slot}
-
- ))}
-
-
-
-
-
-
-
- {[...years]
- .sort((a, b) => Number(b) - Number(a))
- .map((year) => (
-
- {year}
-
- ))}
-
-
-
-
-
- Submit
-
-
-
-
- {isLoading
- ? Array.from({ length: 4 }).map((_, idx) => (
-
- {/* Top section */}
-
-
-
-
-
- {/* Middle section */}
-
-
- ))
- : displayPapers.map((paper, subIndex) => (
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/src/db/relatedSubjects.ts b/src/db/relatedSubjects.ts
new file mode 100644
index 0000000..c9a4c27
--- /dev/null
+++ b/src/db/relatedSubjects.ts
@@ -0,0 +1,14 @@
+import mongoose, { Schema, type Model } from "mongoose";
+import { type IRelatedSubject } from "@/interface";
+
+
+const relatedSubjectSchema = new Schema({
+ subject: { type: String, required: true },
+ related_subjects: { type: [String], required: true },
+});
+
+const RelatedSubject: Model =
+ mongoose.models.RelatedSubject ??
+ mongoose.model("RelatedSubject", relatedSubjectSchema);
+
+export default RelatedSubject;
diff --git a/src/interface.ts b/src/interface.ts
index 74bcfca..c8362da 100644
--- a/src/interface.ts
+++ b/src/interface.ts
@@ -204,3 +204,14 @@ export interface Filters {
export interface StoredSubjects {
subjects: string[];
}
+
+export interface TransformedPaper {
+ subject: string;
+ slots: string[];
+}
+
+
+export interface IRelatedSubject {
+ subject: string;
+ related_subjects: string[];
+}
\ No newline at end of file
diff --git a/src/lib/mongoose.ts b/src/lib/mongoose.ts
index 7f129fe..7be32c3 100644
--- a/src/lib/mongoose.ts
+++ b/src/lib/mongoose.ts
@@ -1,27 +1,41 @@
import mongoose from "mongoose";
-
-if (!process.env.MONGODB_URI) {
- throw new Error("Please add your Mongo URI to .env.local");
+declare global {
+ // eslint-disable-next-line no-var
+ var mongoose: { conn: mongoose.Mongoose | null; promise: Promise | null } | undefined; // This must be a `var` and not a `let / const`
}
-const uri = process.env.MONGODB_URI;
+let cached = global.mongoose;
-let isConnected = false;
+cached ??= global.mongoose = { conn: null, promise: null };
-export const connectToDatabase = async () => {
- if (isConnected) {
- return;
- }
+export async function connectToDatabase() {
+ const MONGODB_URI = process.env.MONGODB_URI!;
- if (mongoose.connection.readyState === mongoose.ConnectionStates.connected) {
- isConnected = true;
- return;
+ if (!MONGODB_URI) {
+ throw new Error(
+ "Please define the MONGODB_URI environment variable inside .env.local",
+ );
}
+ if (cached?.conn) {
+ return cached.conn;
+ }
+ if (cached && !cached.promise) {
+ const opts = {
+ bufferCommands: false,
+ };
+ cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => {
+ return mongoose;
+ });
+ }
try {
- await mongoose.connect(uri);
- isConnected = true;
- } catch (error) {
- throw new Error("Failed to connect to MongoDB");
+ cached!.conn = await cached!.promise;
+ } catch (e) {
+ if (cached) {
+ cached.promise = null;
+ }
+ throw e;
}
-};
+
+ return cached?.conn;
+}
diff --git a/src/util/download_paper.tsx b/src/util/download.tsx
similarity index 100%
rename from src/util/download_paper.tsx
rename to src/util/download.tsx