Skip to content
15 changes: 8 additions & 7 deletions src/app/api/papers/count/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@ import Paper from "@/db/papers";

export const dynamic = "force-dynamic";

export async function GET() {
export async function GET(req: Request) {
try {
await connectToDatabase();

const count: number = await Paper.countDocuments();
const { searchParams } = new URL(req.url);
const subject = searchParams.get("subject");
Comment on lines +11 to +12
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

req.query.subject should work


return NextResponse.json(
{ count },
{ status: 200 }
);
const filter = subject ? { subject } : {};
const count = await Paper.countDocuments(filter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just maintain a counter in db
and increment those values on the fly


return NextResponse.json({ count }, { status: 200 });
} catch (error) {
return NextResponse.json(
{ message: "Failed to fetch papers", error },
{ status: 500 }
{ status: 500 },
);
}
}
2 changes: 1 addition & 1 deletion src/app/api/papers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export async function GET(req: NextRequest) {
if (papers.length === 0) {
return NextResponse.json(
{ message: "No papers found for the specified subject" },
{ status: 404 },
{ status: 200 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors shouldn't be 200

);
}

Expand Down
9 changes: 7 additions & 2 deletions src/app/api/upcoming-papers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ export async function GET() {
{ status: 404 },
);
}
const nextSlot = String.fromCharCode(slot.charCodeAt(0) + 1)
const correspondingSlots = [slot + "1", slot + "2", nextSlot + "1", nextSlot + "2"];
const nextSlot = String.fromCharCode(slot.charCodeAt(0) + 1);
const correspondingSlots = [
slot + "1",
slot + "2",
nextSlot + "1",
nextSlot + "2",
];
const selectedSubjects = await UpcomingSubject.find({
slots: { $in: correspondingSlots },
});
Expand Down
53 changes: 53 additions & 0 deletions src/app/api/user-papers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { NextResponse } from "next/server";
import { connectToDatabase } from "@/lib/mongoose";
import Paper from "@/db/papers";
import { StoredSubjects } from "@/interface";

export const dynamic = "force-dynamic";

export async function POST(req: Request) {
try {
await connectToDatabase();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will happen at startup u dont need to do this

const body = (await req.json()) as StoredSubjects;

const subjects = body;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant


const usersPapers = await Paper.find({
subject: { $in: subjects },
});

const transformedPapers = usersPapers.reduce(
(acc: { subject: string; slots: string[] }[], paper) => {
const existing = acc.find((item) => item.subject === paper.subject);

if (existing) {
existing.slots.push(paper.slot);
} else {
acc.push({ subject: paper.subject, slots: [paper.slot] });
}

return acc;
},
[],
);

const seenSubjects = new Set();
const uniquePapers = transformedPapers.filter((paper) => {
if (seenSubjects.has(paper.subject)) return false;
seenSubjects.add(paper.subject);
return true;
});

return NextResponse.json(uniquePapers, {
status: 200,
});
} catch (error) {
console.error("Error fetching papers:", error);
return NextResponse.json(
{
error: "Failed to fetch papers.",
},
{ status: 500 },
);
}
}
25 changes: 25 additions & 0 deletions src/app/pinned/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import SearchBar from "@/components/Searchbar/searchbar";
import PinnedPapersCarousel from "@/components/PinnedPapersCarousel";

const Pinned = () => {
return (
<div id="pinned" className="flex flex-col justify-between">
<h1 className="mx-auto my-8 text-center font-vipnabd text-3xl font-extrabold">
Pinned Papers
</h1>

<div className="flex items-center justify-center gap-4 px-6">
<div className="flex max-w-xl flex-1">
<SearchBar type="pinned" />
</div>
</div>
<PinnedPapersCarousel carouselType="users" />
<div className="mt-6 flex w-full items-center justify-center">
<p>You can pin upto 8 Subjects</p>
</div>
</div>
);
};

export default Pinned;
14 changes: 14 additions & 0 deletions src/components/AddPapers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const AddPapers = ({ onClick }: { onClick?: () => void }) => {
return (
<div
onClick={onClick}
className="flex h-full w-full cursor-pointer items-center justify-center rounded-md border-2 border-dashed border-purple-500 bg-transparent transition hover:border-gray-400 hover:bg-[#2a2a2a] hover:text-gray-300"
>
<span className="text-4xl text-purple-500 transition-colors duration-200 group-hover:text-gray-300">
+
</span>
</div>
);
};

export default AddPapers;
52 changes: 49 additions & 3 deletions src/components/CatalogueContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import SideBar from "../components/SideBar";
import Error from "./Error";
import { Filter } from "lucide-react";
import { Sheet, SheetContent, SheetTrigger } from "./ui/sheet";
import { StarIcon } from "lucide-react";
import { StoredSubjects } from "@/interface";

export async function downloadFile(url: string, filename: string) {
try {
Expand Down Expand Up @@ -48,25 +50,51 @@ const CatalogueContent = () => {
const [filterOptions, setFilterOptions] = useState<Filters>();
const [filtersPulled, setFiltersPulled] = useState<boolean>(false);
const [appliedFilters, setAppliedFilters] = useState<boolean>(false);
const [pinned, setPinned] = useState<boolean>(false);

// Set initial state from searchParams on client-side mount
useEffect(() => {
setIsMounted(true);
if (searchParams) {
setSubject(searchParams.get("subject"));
const currentPinnedSubjects = JSON.parse(
localStorage.getItem("userSubjects") ?? "[]",
) as StoredSubjects;
const subjectName = searchParams.get("subject");
setSubject(subjectName);
setSelectedExams(searchParams.get("exams")?.split(",") ?? []);
setSelectedSlots(searchParams.get("slots")?.split(",") ?? []);
setSelectedYears(searchParams.get("years")?.split(",") ?? []);
setSelectedCampuses(searchParams.get("campus")?.split(",") ?? []);
setSelectedSemesters(searchParams.get("semester")?.split(",") ?? []);
setSelectedAnswerKeyIncluded(searchParams.get("answerkey") === "true");
if (subjectName && Array.isArray(currentPinnedSubjects)) {
if (currentPinnedSubjects.includes(subjectName)) {
setPinned(true);
} else {
setPinned(false);
}
}
}
}, [searchParams]);
}, [searchParams, pinned]);

const filtersNotPulled = () => {
setFiltersPulled(false);
};

const handlePinToggle = () => {
const current = !pinned;
setPinned(current);

const saved = JSON.parse(
localStorage.getItem("userSubjects") ?? "[]",
) as string[];
const updated = current
? [...new Set([...saved, subject])]
: saved.filter((s) => s !== subject);

localStorage.setItem("userSubjects", JSON.stringify(updated));
};

// Fetch papers and apply filters
useEffect(() => {
if (!subject || !isMounted) return;
Expand Down Expand Up @@ -295,11 +323,29 @@ const CatalogueContent = () => {
</SheetContent>
</Sheet>

<div className="flex items-center gap-2 p-7">
<div>
<p className="text-s font-semibold text-white/80">
{subject?.split("[")[1]?.replace("]", "")}
</p>
<h2 className="text-2xl font-extrabold text-white md:text-3xl">
{subject?.split(" [")[0]}
</h2>
</div>
<div className="mt-7">
<button onClick={handlePinToggle}>
<StarIcon
className={`h-7 w-7 ${pinned ? "fill-[#A78BFA]" : ""} stroke-white`}
/>
</button>
</div>
</div>

{loading ? (
<Loader />
) : papers.length > 0 ? (
<div
className={`grid h-fit grid-cols-1 gap-8 px-[30px] py-[40px] md:grid-cols-2 lg:grid-cols-4 ${filtersPulled ? "blur-xl" : ""}`}
className={`grid h-fit grid-cols-1 gap-8 px-[30px] pb-[40px] md:grid-cols-2 lg:grid-cols-4 ${filtersPulled ? "blur-xl" : ""}`}
>
{appliedFilters ? (
filteredPapers.length > 0 ? (
Expand Down
9 changes: 8 additions & 1 deletion src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ModeToggle from "@/components/toggle-theme";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { ArrowDownLeftIcon } from "lucide-react";
import { StarIcon } from "lucide-react";

function Navbar() {
const pathname = usePathname();
Expand All @@ -27,10 +28,16 @@ function Navbar() {
</a>
<Link
href="/"
className="font-jost bg-gradient-to-r from-[#562EE7] to-[rgba(116,128,255,0.8)] bg-clip-text text-left text-4xl font-bold tracking-wide text-transparent dark:from-[#562EE7] dark:to-[#FFC6E8] md:text-6xl"
className="bg-gradient-to-r from-[#562EE7] to-[rgba(116,128,255,0.8)] bg-clip-text text-left font-jost text-4xl font-bold tracking-wide text-transparent dark:from-[#562EE7] dark:to-[#FFC6E8] md:text-6xl"
>
Papers
</Link>
<Link href="/pinned">
<div className="ml-3 mt-4 flex items-center gap-1 rounded-full border border-white/20 bg-[#1A1921] px-4 py-[6px] text-xs font-semibold text-white shadow-sm transition hover:bg-[#2C2A36] dark:border-[#36266D] dark:bg-[#171720] dark:hover:bg-[#2A2635] sm:text-sm">
<StarIcon className="h-4 w-4" />
Pinned Subjects
</div>
</Link>
</div>
<div className="md:w/[20%] flex items-center justify-end gap-x-2">
<div className="scale-75 sm:scale-100">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import Autoplay from "embla-carousel-autoplay";
import { chunkArray } from "@/util/utils";

function StoredPapers() {
function PapersCarousel() {
const [displayPapers, setDisplayPapers] = useState<IUpcomingPaper[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [chunkSize, setChunkSize] = useState<number>(4);
Expand Down Expand Up @@ -107,4 +107,4 @@ function StoredPapers() {
);
}

export default StoredPapers;
export default PapersCarousel;
27 changes: 27 additions & 0 deletions src/components/PinButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import { Star, StarOff } from "lucide-react";

export default function PinButton({
isPinned,
onToggle,
}: {
isPinned: boolean;
onToggle?: () => void;
}) {
return (
<button
onClick={onToggle}
className={`ml-2 flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium ${
isPinned ? "bg-purple-700 text-white" : "bg-[#2B2B30] text-white/80"
} transition hover:bg-purple-600`}
>
{isPinned ? (
<Star className="h-4 w-4" />
) : (
<StarOff className="h-4 w-4" />
)}
<span className="hidden sm:inline">{isPinned ? "Pinned" : "Pin"}</span>
</button>
);
}
Loading