From 5da17de13426d10b1580246ae0925bd55f973057 Mon Sep 17 00:00:00 2001 From: advikgupta Date: Sat, 7 Jun 2025 17:13:58 +0530 Subject: [PATCH 1/4] created users paper section --- src/app/api/upcoming-papers/route.ts | 9 +- src/app/api/user-papers/route.ts | 42 ++++++++++ src/components/UsersPapers.tsx | 120 +++++++++++++++++++++++++++ src/components/screens/Hero.tsx | 4 +- 4 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 src/app/api/user-papers/route.ts create mode 100644 src/components/UsersPapers.tsx diff --git a/src/app/api/upcoming-papers/route.ts b/src/app/api/upcoming-papers/route.ts index 83f78b0..8b426fa 100644 --- a/src/app/api/upcoming-papers/route.ts +++ b/src/app/api/upcoming-papers/route.ts @@ -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 }, }); diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts new file mode 100644 index 0000000..5b552c2 --- /dev/null +++ b/src/app/api/user-papers/route.ts @@ -0,0 +1,42 @@ +import { NextResponse } from "next/server"; +import { connectToDatabase } from "@/lib/mongoose"; +import Paper from "@/db/papers"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + try { + await connectToDatabase(); + const body = await req.json(); + + const subjects: string[] = body; + + const usersPapers = await Paper.find({ + subject: { $in: subjects }, + }); + + let transformedPapers = usersPapers.map((paper) => ({ + subject: paper.subject, + slots: [paper.slot], + })); + + // check duplicates + transformedPapers = Array.from( + new Map(transformedPapers.map((item) => [item.subject, item])).values(), + ); + + console.log("usersPapers", usersPapers); + + return NextResponse.json(transformedPapers, { + status: 200, + }); + } catch (error) { + console.error("Error fetching papers:", error); + return NextResponse.json( + { + error: "Failed to fetch papers.", + }, + { status: 500 }, + ); + } +} diff --git a/src/components/UsersPapers.tsx b/src/components/UsersPapers.tsx new file mode 100644 index 0000000..1a10e33 --- /dev/null +++ b/src/components/UsersPapers.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { useEffect, useState } from "react"; +import axios from "axios"; +import { type IUpcomingPaper } from "@/interface"; +import Loader from "./ui/loader"; +import UpcomingPaper from "./UpcomingPaper"; +import { + Carousel, + CarouselContent, + CarouselItem, + CarouselNext, + CarouselPrevious, +} from "@/components/ui/carousel"; +import Autoplay from "embla-carousel-autoplay"; +import { chunkArray } from "@/util/utils"; + +function UsersPapers() { + const [displayPapers, setDisplayPapers] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [chunkSize, setChunkSize] = useState(4); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth < 640) { + setChunkSize(4); + } else { + setChunkSize(8); + } + }; + + localStorage.setItem( + "userSubjects", + JSON.stringify([ + "Information Security [CBS3002]", + "Foundations of Data Analytics [BCSE351E]", + "Design and Analysis of Algorithms [MCSE502L]", + "Complex Variables and Linear Algebra [BMAT201L]", + "Differential Equations and Transforms [BMAT102L]", + ]), + ); + + handleResize(); + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + const chunkedPapers = chunkArray(displayPapers, chunkSize); + + useEffect(() => { + async function fetchPapers() { + try { + const storedSubjects = JSON.parse(localStorage.getItem("userSubjects")); + setIsLoading(true); + const response = await axios.post("/api/user-papers", storedSubjects); + setDisplayPapers(response.data); + } catch (error) { + console.error("Failed to fetch papers:", error); + } finally { + setIsLoading(false); + } + } + + void fetchPapers(); + }, []); + + if (isLoading) { + return ; + } + + const plugins = [Autoplay({ delay: 8000, stopOnInteraction: true })]; + + return ( +
+

+ Users Papers +

+ +
+ +
+ + +
+ + {chunkedPapers.map((paperGroup, index) => { + return ( + + {paperGroup.map((paper, subIndex) => ( +
+ +
+ ))} +
+ ); + })} +
+
+
+
+ ); +} + +export default UsersPapers; diff --git a/src/components/screens/Hero.tsx b/src/components/screens/Hero.tsx index 3cb0ffc..822a9c7 100644 --- a/src/components/screens/Hero.tsx +++ b/src/components/screens/Hero.tsx @@ -1,16 +1,18 @@ import React from "react"; import SearchBar from "../Searchbar/searchbar"; import StoredPapers from "../StoredPapers"; +import UsersPapers from "../UsersPapers"; const Hero = () => { return (
-

+

Built by Students for Students

+ {/*

Learn More

From 6fb6e87f30f4ea97705a338bf22c75c7c62e17f4 Mon Sep 17 00:00:00 2001 From: advikgupta Date: Sat, 7 Jun 2025 23:03:27 +0530 Subject: [PATCH 2/4] created PapersCarousel template component --- src/app/api/papers/route.ts | 2 +- src/app/api/user-papers/route.ts | 28 +++-- .../{UsersPapers.tsx => PapersCarousel.tsx} | 25 +++- src/components/Searchbar/searchbar-child.tsx | 23 +++- src/components/StoredPapers.tsx | 110 ------------------ src/components/screens/Hero.tsx | 7 +- 6 files changed, 61 insertions(+), 134 deletions(-) rename src/components/{UsersPapers.tsx => PapersCarousel.tsx} (82%) delete mode 100644 src/components/StoredPapers.tsx diff --git a/src/app/api/papers/route.ts b/src/app/api/papers/route.ts index 12186d0..5a33b13 100644 --- a/src/app/api/papers/route.ts +++ b/src/app/api/papers/route.ts @@ -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 }, ); } diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts index 5b552c2..7cef1f4 100644 --- a/src/app/api/user-papers/route.ts +++ b/src/app/api/user-papers/route.ts @@ -15,19 +15,27 @@ export async function POST(req: Request) { subject: { $in: subjects }, }); - let transformedPapers = usersPapers.map((paper) => ({ - subject: paper.subject, - slots: [paper.slot], - })); + const transformedPapers = usersPapers.reduce((acc, paper) => { + const existing = acc.find((item) => item.subject === paper.subject); - // check duplicates - transformedPapers = Array.from( - new Map(transformedPapers.map((item) => [item.subject, item])).values(), - ); + if (existing) { + existing.slots.push(paper.slot); + } else { + acc.push({ subject: paper.subject, slots: [paper.slot] }); + } + + return acc; + }, []); - console.log("usersPapers", usersPapers); + // check duplicates + 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(transformedPapers, { + return NextResponse.json(uniquePapers, { status: 200, }); } catch (error) { diff --git a/src/components/UsersPapers.tsx b/src/components/PapersCarousel.tsx similarity index 82% rename from src/components/UsersPapers.tsx rename to src/components/PapersCarousel.tsx index 1a10e33..287e6a6 100644 --- a/src/components/UsersPapers.tsx +++ b/src/components/PapersCarousel.tsx @@ -15,7 +15,11 @@ import { import Autoplay from "embla-carousel-autoplay"; import { chunkArray } from "@/util/utils"; -function UsersPapers() { +function PapersCarousel({ + carouselType, +}: { + carouselType: "users" | "default"; +}) { const [displayPapers, setDisplayPapers] = useState([]); const [isLoading, setIsLoading] = useState(true); const [chunkSize, setChunkSize] = useState(4); @@ -53,10 +57,19 @@ function UsersPapers() { useEffect(() => { async function fetchPapers() { try { - const storedSubjects = JSON.parse(localStorage.getItem("userSubjects")); setIsLoading(true); - const response = await axios.post("/api/user-papers", storedSubjects); - setDisplayPapers(response.data); + if (carouselType === "users") { + const storedSubjects = JSON.parse( + localStorage.getItem("userSubjects"), + ); + const response = await axios.post("/api/user-papers", storedSubjects); + setDisplayPapers(response.data); + } else { + const response = await axios.get( + "/api/upcoming-papers", + ); + setDisplayPapers(response.data); + } } catch (error) { console.error("Failed to fetch papers:", error); } finally { @@ -76,7 +89,7 @@ function UsersPapers() { return (

- Users Papers + {carouselType === "users" ? "Your Papers" : "Upcoming Papers"}

@@ -117,4 +130,4 @@ function UsersPapers() { ); } -export default UsersPapers; +export default PapersCarousel; diff --git a/src/components/Searchbar/searchbar-child.tsx b/src/components/Searchbar/searchbar-child.tsx index 4359731..3e7ba10 100644 --- a/src/components/Searchbar/searchbar-child.tsx +++ b/src/components/Searchbar/searchbar-child.tsx @@ -25,9 +25,17 @@ function SearchBarChild({ const response = await axios.get("/api/papers", { params: { subject: subjectName }, }); - return response.data.papers.length; // Assuming the API returns an array of papers + + if ( + response.data.message === "No papers found for the specified subject" + ) { + return 0; + } + + return response.data.papers.length; } catch (error) { - return 0; // Return 0 if there's no papers found or an error occurs + console.error("Error fetching paper quantity:", error); + return "request-error"; } }; @@ -44,7 +52,16 @@ function SearchBarChild({ .map((item) => item.item) .slice(0, 10); - setSuggestions(filteredSuggestions); + const suggestionsWithCount = await Promise.all( + filteredSuggestions.map(async (suggestion) => { + const count = await fetchPaperQuantityByName(suggestion); + return count !== "request-error" + ? `${suggestion} (${count})` + : suggestion; + }), + ); + + setSuggestions(suggestionsWithCount); } else { setSuggestions([]); } diff --git a/src/components/StoredPapers.tsx b/src/components/StoredPapers.tsx deleted file mode 100644 index 5e84ce3..0000000 --- a/src/components/StoredPapers.tsx +++ /dev/null @@ -1,110 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import axios from "axios"; -import { type IUpcomingPaper } from "@/interface"; -import Loader from "./ui/loader"; -import UpcomingPaper from "./UpcomingPaper"; -import { - Carousel, - CarouselContent, - CarouselItem, - CarouselNext, - CarouselPrevious, -} from "@/components/ui/carousel"; -import Autoplay from "embla-carousel-autoplay"; -import { chunkArray } from "@/util/utils"; - -function StoredPapers() { - const [displayPapers, setDisplayPapers] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [chunkSize, setChunkSize] = useState(4); - - useEffect(() => { - const handleResize = () => { - if (window.innerWidth < 640) { - setChunkSize(4); - } else { - setChunkSize(8); - } - }; - - handleResize(); - window.addEventListener("resize", handleResize); - - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); - - const chunkedPapers = chunkArray(displayPapers, chunkSize); - - useEffect(() => { - async function fetchPapers() { - try { - setIsLoading(true); - const response = await axios.get( - "/api/upcoming-papers", - ); - setDisplayPapers(response.data); - } catch (error) { - console.error("Failed to fetch papers:", error); - } finally { - setIsLoading(false); - } - } - - void fetchPapers(); - }, []); - - if (isLoading) { - return ; - } - - const plugins = [Autoplay({ delay: 8000, stopOnInteraction: true })]; - - return ( -
-

- Upcoming Papers -

- -
- -
- - -
- - {chunkedPapers.map((paperGroup, index) => { - return ( - - {paperGroup.map((paper, subIndex) => ( -
- -
- ))} -
- ); - })} -
-
-
-
- ); -} - -export default StoredPapers; diff --git a/src/components/screens/Hero.tsx b/src/components/screens/Hero.tsx index 822a9c7..b85e9dd 100644 --- a/src/components/screens/Hero.tsx +++ b/src/components/screens/Hero.tsx @@ -1,7 +1,6 @@ import React from "react"; import SearchBar from "../Searchbar/searchbar"; -import StoredPapers from "../StoredPapers"; -import UsersPapers from "../UsersPapers"; +import PapersCarousel from "../PapersCarousel"; const Hero = () => { return ( @@ -12,8 +11,8 @@ const Hero = () => {
- - + + {/*

Learn More

Date: Fri, 13 Jun 2025 00:49:47 +0530 Subject: [PATCH 3/4] made paper count route in api --- src/app/api/papers/count/route.ts | 15 +++++----- src/components/Searchbar/searchbar-child.tsx | 30 +------------------- 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/src/app/api/papers/count/route.ts b/src/app/api/papers/count/route.ts index 48f3bbc..1dba8e4 100644 --- a/src/app/api/papers/count/route.ts +++ b/src/app/api/papers/count/route.ts @@ -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"); - return NextResponse.json( - { count }, - { status: 200 } - ); + const filter = subject ? { subject } : {}; + const count = await Paper.countDocuments(filter); + + return NextResponse.json({ count }, { status: 200 }); } catch (error) { return NextResponse.json( { message: "Failed to fetch papers", error }, - { status: 500 } + { status: 500 }, ); } } diff --git a/src/components/Searchbar/searchbar-child.tsx b/src/components/Searchbar/searchbar-child.tsx index 3e7ba10..7d44e17 100644 --- a/src/components/Searchbar/searchbar-child.tsx +++ b/src/components/Searchbar/searchbar-child.tsx @@ -20,25 +20,6 @@ function SearchBarChild({ const suggestionsRef = useRef(null); const fuzzy = new Fuse(initialSubjects); - const fetchPaperQuantityByName = async (subjectName: string) => { - try { - const response = await axios.get("/api/papers", { - params: { subject: subjectName }, - }); - - if ( - response.data.message === "No papers found for the specified subject" - ) { - return 0; - } - - return response.data.papers.length; - } catch (error) { - console.error("Error fetching paper quantity:", error); - return "request-error"; - } - }; - const handleSearchChange = async (e: React.ChangeEvent) => { const text = e.target.value; setSearchText(text); @@ -52,16 +33,7 @@ function SearchBarChild({ .map((item) => item.item) .slice(0, 10); - const suggestionsWithCount = await Promise.all( - filteredSuggestions.map(async (suggestion) => { - const count = await fetchPaperQuantityByName(suggestion); - return count !== "request-error" - ? `${suggestion} (${count})` - : suggestion; - }), - ); - - setSuggestions(suggestionsWithCount); + setSuggestions(filteredSuggestions); } else { setSuggestions([]); } From f1ad13050c6eaab84cee4ac51e87122bc3d9aed5 Mon Sep 17 00:00:00 2001 From: Advik-Gupta Date: Wed, 18 Jun 2025 14:14:46 +0530 Subject: [PATCH 4/4] made carousel type default to upcoming --- src/components/PapersCarousel.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/PapersCarousel.tsx b/src/components/PapersCarousel.tsx index 287e6a6..a00e968 100644 --- a/src/components/PapersCarousel.tsx +++ b/src/components/PapersCarousel.tsx @@ -16,9 +16,9 @@ import Autoplay from "embla-carousel-autoplay"; import { chunkArray } from "@/util/utils"; function PapersCarousel({ - carouselType, + carouselType = "upcoming", }: { - carouselType: "users" | "default"; + carouselType: "users" | "upcoming"; }) { const [displayPapers, setDisplayPapers] = useState([]); const [isLoading, setIsLoading] = useState(true);