Skip to content

merge prod to staging #246

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
22 commits merged into from
Aug 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a9e2e04
Update README.md
abhitrueprogrammer Aug 10, 2025
4ee3c39
Merge branch 'CodeChefVIT:staging' into staging
abhitrueprogrammer Aug 10, 2025
27f9403
Merge pull request #1 from abhitrueprogrammer/staging
abhitrueprogrammer Aug 10, 2025
499b1e4
Merge branch 'CodeChefVIT:staging' into staging
abhitrueprogrammer Aug 11, 2025
31b0098
Merge pull request #2 from abhitrueprogrammer/staging
abhitrueprogrammer Aug 11, 2025
71338ae
Rename download_paper.tsx to download.tsx
abhitrueprogrammer Aug 11, 2025
f2cf1d4
Update README.md
abhitrueprogrammer Aug 11, 2025
5724e11
light mode
abhitrueprogrammer Aug 11, 2025
cebf008
refactor
abhitrueprogrammer Aug 11, 2025
fce5581
Merge pull request #242 from abhitrueprogrammer/prod
Abh1noob Aug 12, 2025
10ca176
alert -> toast
abhitrueprogrammer Aug 12, 2025
001c280
Merge branch 'CodeChefVIT:prod' into prod
abhitrueprogrammer Aug 12, 2025
b5d1d7f
Merge pull request #243 from abhitrueprogrammer/prod
abhitrueprogrammer Aug 12, 2025
d77d08b
mongo db multiple connections fix
abhitrueprogrammer Aug 12, 2025
e02e6b2
Merge remote-tracking branch 'refs/remotes/origin/prod' into prod
abhitrueprogrammer Aug 12, 2025
023a0dd
feat: related subs
abhitrueprogrammer Aug 12, 2025
971dab0
fix: added back searchbar into navbar and added dropdown for buttons
shikhar-sahay Aug 13, 2025
859190a
Used shadCN component for dropdown menu
shikhar-sahay Aug 14, 2025
ff6b388
Merge pull request #249 from shikhar-sahay/prod
abhitrueprogrammer Aug 14, 2025
501d7ae
Revert "fix: added back searchbar into navbar and added dropdown for …
abhitrueprogrammer Aug 14, 2025
7da80d6
Merge pull request #251 from CodeChefVIT/revert-249-prod
abhitrueprogrammer Aug 14, 2025
c062727
Merge pull request #247 from abhitrueprogrammer/related
abhitrueprogrammer Aug 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<h2 align="center"> Papers </h2>
<br/>


> <p>Prepare to excel in your CATs and FATs with CodeChef-VIT's dedicated repository of past exam papers. Access key resources to review concepts, tackle challenging questions, and familiarize yourself with exam patterns. Boost your confidence, sharpen your strategy, and get ready to ace your exams!</p>
## 🌐 Deploy
Expand All @@ -17,7 +18,6 @@
- MongoDB & Mongoose : Database and object data modeling (ODM) for Node.js.
- Cloudinary : Media storage and optimization service.
- Shadcn : Collection of pre-built components using Radix UI and Tailwind CSS.

## 💡 Features:

- Access a vast collection of past CAT and FAT papers
Expand Down
43 changes: 0 additions & 43 deletions ongoing-papers.ts

This file was deleted.

41 changes: 41 additions & 0 deletions src/app/api/related-subject/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NextResponse, type NextRequest } from "next/server";
import { connectToDatabase } from "@/lib/mongoose";
import { IRelatedSubject } from "@/interface";
import RelatedSubject from "@/db/relatedSubjects";

export const dynamic = "force-dynamic";

export async function GET(req: NextRequest) {
try {
await connectToDatabase();
const url = req.nextUrl.searchParams;
const subject = url.get("subject");
const escapeRegExp = (text: string) => {
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
const escapedSubject = escapeRegExp(subject ?? "");

if (!subject) {
return NextResponse.json(
{ message: "Subject query parameter is required" },
{ status: 400 },
);
}
const subjects: IRelatedSubject[] = await RelatedSubject.find({
subject: { $regex: new RegExp(`${escapedSubject}`, "i") },
});
console.log("realted", subjects);

return NextResponse.json(
{
related_subjects: subjects[0]?.related_subjects
},
{ status: 200 },
);
} catch (error) {
return NextResponse.json(
{ message: "Failed to fetch related subject", error },
{ status: 500 },
);
}
}
4 changes: 3 additions & 1 deletion src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,7 @@ async function CreatePDF(orderedFiles: File[]) {
}

const mergedPdfBytes = await pdfDoc.save();
return mergedPdfBytes;
const ab = new ArrayBuffer(mergedPdfBytes.byteLength);
new Uint8Array(ab).set(mergedPdfBytes);
return ab;
}
6 changes: 1 addition & 5 deletions src/app/api/user-papers/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import { NextResponse } from "next/server";
import { connectToDatabase } from "@/lib/mongoose";
import Paper from "@/db/papers";
import { StoredSubjects } from "@/interface";
import { StoredSubjects, TransformedPaper } from "@/interface";

export const dynamic = "force-dynamic";

interface TransformedPaper {
subject: string;
slots: string[];
}

export async function POST(req: Request) {
try {
Expand Down
270 changes: 267 additions & 3 deletions src/app/request/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,269 @@
import PapersPage from "@/components/screens/PapersPage";
"use client";

export default function RequestPage() {
return <PapersPage />;
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 { type IUpcomingPaper } from "@/interface";
import { Skeleton } from "../../components/ui/skeleton";
import UpcomingPaper from "../../components/UpcomingPaper";
import toast from "react-hot-toast";
import { Search } from "lucide-react";
import SkeletonPaperCard from "@/components/SkeletonPaperCard";

type Course = {
name?: string | null;
courseName?: string | null;
title?: string | null;
};

export default function PaperRequest() {
const [subjects, setSubjects] = useState<string[]>([]);
const [searchText, setSearchText] = useState("");
const [suggestions, setSuggestions] = useState<string[]>([]);
const [selectedSubject, setSelectedSubject] = useState<string | null>(null);
const [selectedExam, setSelectedExam] = useState<string | null>(null);
const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
const [selectedYear, setSelectedYear] = useState<string | null>(null);
const suggestionsRef = useRef<HTMLUListElement | null>(null);
const [displayPapers, setDisplayPapers] = useState<IUpcomingPaper[]>([]);
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
async function fetchSubjects() {
try {
const response = await axios.get<Course[]>(`/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<IUpcomingPaper[]>(
"/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) {
toast.error("Please fill all fields before submitting.");
return;
}

try {
await toast.promise(
axios.post("/api/request", {
subject: selectedSubject,
exam: selectedExam,
slot: selectedSlot,
year: selectedYear,
}),
{
loading: "Submitting your request...",
success: "Your paper request was submitted successfully",
error: "Failed to submit your request. Please try again later.",
}
);

setSearchText("");
setSelectedSubject(null);
setSelectedExam(null);
setSelectedSlot(null);
setSelectedYear(null);
} catch (error) {
console.error("Error submitting request:", error);
}
};

return (
<div className="min-h-screen bg-[#F3F5FF] px-6 py-12 text-black dark:bg-[#070114] dark:text-white">
<main>
<div className="mx-auto mb-16 max-w-4xl text-center">
<h2 className="mb-12 font-vipnabd text-3xl font-extrabold md:text-4xl">
Specific Paper Request
</h2>

<div className="relative mx-auto mb-8 max-w-xl font-play">
<Input
type="text"
value={searchText}
onChange={(e) => 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" : ""}`}
/>
<button
type="button"
className="absolute inset-y-0 right-0 flex items-center pr-3"
>
<Search className="h-5 w-5 text-black dark:text-white" />{" "}
</button>
{suggestions.length > 0 && (
<ul
ref={suggestionsRef}
className="absolute z-20 max-h-[250px] w-full max-w-xl overflow-y-auto rounded-md rounded-t-none border border-t-0 bg-white text-center shadow-lg dark:bg-[#303771]"
>
{suggestions.map((s, idx) => (
<li
key={idx}
onClick={() => handleSelectSubject(s)}
className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
>
{s}
</li>
))}
</ul>
)}
</div>

<div className="mb-8 flex justify-center gap-4">
<Select
onValueChange={setSelectedExam}
disabled={!selectedSubject}
value={selectedExam ?? undefined}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Exam" />
</SelectTrigger>
<SelectContent>
{exams.map((exam) => (
<SelectItem key={exam} value={exam}>
{exam}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
onValueChange={setSelectedSlot}
disabled={!selectedSubject}
value={selectedSlot ?? undefined}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Slot" />
</SelectTrigger>
<SelectContent>
{slots.map((slot) => (
<SelectItem key={slot} value={slot}>
{slot}
</SelectItem>
))}
</SelectContent>
</Select>
<Select
onValueChange={setSelectedYear}
disabled={!selectedSubject}
value={selectedYear ?? undefined}
>
<SelectTrigger className="w-32">
<SelectValue placeholder="Year" />
</SelectTrigger>
<SelectContent>
{[...years]
.sort((a, b) => Number(b) - Number(a))
.map((year) => (
<SelectItem key={year} value={year}>
{year}
</SelectItem>
))}
</SelectContent>
</Select>
</div>

<Button
className="rounded-lg bg-[#4A55FF] px-8 py-3 text-base text-white hover:bg-[#3A44CC] dark:bg-[#9EA8FF] dark:text-black dark:hover:bg-[#7D86E5]"
onClick={handleSubmit}
>
Submit
</Button>
</div>

<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
{isLoading ? (
<SkeletonPaperCard length={4} />
) : (
displayPapers.map((paper, subIndex) => (
<div key={subIndex} className="h-full">
<UpcomingPaper subject={paper.subject} slots={paper.slots} />
</div>
))
)}
</div>
</main>
</div>
);
}
Loading