Skip to content

Commit ebfd8a3

Browse files
Merge branch 'prod' into staging
2 parents ab0389c + e02e6b2 commit ebfd8a3

File tree

17 files changed

+356
-419
lines changed

17 files changed

+356
-419
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<h2 align="center"> Papers </h2>
55
<br/>
66

7+
78
> <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>
89
910
## 🌐 Deploy

ongoing-papers.ts

Lines changed: 0 additions & 43 deletions
This file was deleted.

src/app/api/upload/route.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,5 +181,7 @@ async function CreatePDF(orderedFiles: File[]) {
181181
}
182182

183183
const mergedPdfBytes = await pdfDoc.save();
184-
return mergedPdfBytes;
184+
const ab = new ArrayBuffer(mergedPdfBytes.byteLength);
185+
new Uint8Array(ab).set(mergedPdfBytes);
186+
return ab;
185187
}

src/app/api/user-papers/route.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
import { NextResponse } from "next/server";
22
import { connectToDatabase } from "@/lib/mongoose";
33
import Paper from "@/db/papers";
4-
import { StoredSubjects } from "@/interface";
4+
import { StoredSubjects, TransformedPaper } from "@/interface";
55

66
export const dynamic = "force-dynamic";
77

8-
interface TransformedPaper {
9-
subject: string;
10-
slots: string[];
11-
}
128

139
export async function POST(req: Request) {
1410
try {

src/app/request/page.tsx

Lines changed: 267 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,269 @@
1-
import PapersPage from "@/components/screens/PapersPage";
1+
"use client";
22

3-
export default function RequestPage() {
4-
return <PapersPage />;
3+
import { useEffect, useState, useRef, useMemo } from "react";
4+
import { Button } from "@/components/ui/button";
5+
import {
6+
Select,
7+
SelectContent,
8+
SelectItem,
9+
SelectTrigger,
10+
SelectValue,
11+
} from "@/components/ui/select";
12+
import { exams, slots, years } from "@/components/select_options";
13+
import { Input } from "@/components/ui/input";
14+
import axios from "axios";
15+
import Fuse from "fuse.js";
16+
import { type IUpcomingPaper } from "@/interface";
17+
import { Skeleton } from "../../components/ui/skeleton";
18+
import UpcomingPaper from "../../components/UpcomingPaper";
19+
import toast from "react-hot-toast";
20+
import { Search } from "lucide-react";
21+
import SkeletonPaperCard from "@/components/SkeletonPaperCard";
22+
23+
type Course = {
24+
name?: string | null;
25+
courseName?: string | null;
26+
title?: string | null;
27+
};
28+
29+
export default function PaperRequest() {
30+
const [subjects, setSubjects] = useState<string[]>([]);
31+
const [searchText, setSearchText] = useState("");
32+
const [suggestions, setSuggestions] = useState<string[]>([]);
33+
const [selectedSubject, setSelectedSubject] = useState<string | null>(null);
34+
const [selectedExam, setSelectedExam] = useState<string | null>(null);
35+
const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
36+
const [selectedYear, setSelectedYear] = useState<string | null>(null);
37+
const suggestionsRef = useRef<HTMLUListElement | null>(null);
38+
const [displayPapers, setDisplayPapers] = useState<IUpcomingPaper[]>([]);
39+
const [isLoading, setIsLoading] = useState(true);
40+
41+
useEffect(() => {
42+
async function fetchSubjects() {
43+
try {
44+
const response = await axios.get<Course[]>(`/api/course-list`);
45+
const courses: Course[] = response.data;
46+
const names = courses
47+
.map((course) => course.name ?? course.courseName ?? course.title)
48+
.filter(Boolean) as string[];
49+
50+
setSubjects(names);
51+
} catch (err) {
52+
console.error("Error fetching subjects:", err);
53+
}
54+
}
55+
void fetchSubjects();
56+
}, []);
57+
58+
useEffect(() => {
59+
async function fetchPapers() {
60+
try {
61+
setIsLoading(true);
62+
const response = await axios.get<IUpcomingPaper[]>(
63+
"/api/upcoming-papers",
64+
);
65+
66+
const randomPapers = [...response.data]
67+
.sort(() => Math.random() - 0.5)
68+
.slice(0, 4);
69+
70+
setDisplayPapers(randomPapers);
71+
} catch (error) {
72+
console.error("Failed to fetch papers:", error);
73+
} finally {
74+
setIsLoading(false);
75+
}
76+
}
77+
78+
void fetchPapers();
79+
}, []);
80+
81+
const fuse = useMemo(
82+
() => new Fuse(subjects, { includeScore: true, threshold: 0.3 }),
83+
[subjects],
84+
);
85+
86+
useEffect(() => {
87+
if (!searchText.trim()) {
88+
setSuggestions([]);
89+
return;
90+
}
91+
92+
if (selectedSubject && searchText === selectedSubject) {
93+
setSuggestions([]);
94+
return;
95+
}
96+
97+
const results = fuse.search(searchText);
98+
setSuggestions(results.map((r) => r.item).slice(0, 10));
99+
}, [searchText, fuse, selectedSubject]);
100+
101+
const handleSelectSubject = (subject: string) => {
102+
setSelectedSubject(subject);
103+
setSearchText(subject);
104+
setSuggestions([]);
105+
setSelectedExam(null);
106+
setSelectedSlot(null);
107+
setSelectedYear(null);
108+
};
109+
110+
useEffect(() => {
111+
function handleClickOutside(event: MouseEvent) {
112+
if (
113+
suggestionsRef.current &&
114+
!suggestionsRef.current.contains(event.target as Node)
115+
) {
116+
setSuggestions([]);
117+
}
118+
}
119+
document.addEventListener("mousedown", handleClickOutside);
120+
return () => document.removeEventListener("mousedown", handleClickOutside);
121+
}, []);
122+
123+
const handleSubmit = async () => {
124+
if (!selectedSubject || !selectedExam || !selectedSlot || !selectedYear) {
125+
toast.error("Please fill all fields before submitting.");
126+
return;
127+
}
128+
129+
try {
130+
await toast.promise(
131+
axios.post("/api/request", {
132+
subject: selectedSubject,
133+
exam: selectedExam,
134+
slot: selectedSlot,
135+
year: selectedYear,
136+
}),
137+
{
138+
loading: "Submitting your request...",
139+
success: "Your paper request was submitted successfully",
140+
error: "Failed to submit your request. Please try again later.",
141+
}
142+
);
143+
144+
setSearchText("");
145+
setSelectedSubject(null);
146+
setSelectedExam(null);
147+
setSelectedSlot(null);
148+
setSelectedYear(null);
149+
} catch (error) {
150+
console.error("Error submitting request:", error);
151+
}
152+
};
153+
154+
return (
155+
<div className="min-h-screen bg-[#F3F5FF] px-6 py-12 text-black dark:bg-[#070114] dark:text-white">
156+
<main>
157+
<div className="mx-auto mb-16 max-w-4xl text-center">
158+
<h2 className="mb-12 font-vipnabd text-3xl font-extrabold md:text-4xl">
159+
Specific Paper Request
160+
</h2>
161+
162+
<div className="relative mx-auto mb-8 max-w-xl font-play">
163+
<Input
164+
type="text"
165+
value={searchText}
166+
onChange={(e) => setSearchText(e.target.value)}
167+
placeholder="Search by subject..."
168+
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" : ""}`}
169+
/>
170+
<button
171+
type="button"
172+
className="absolute inset-y-0 right-0 flex items-center pr-3"
173+
>
174+
<Search className="h-5 w-5 text-black dark:text-white" />{" "}
175+
</button>
176+
{suggestions.length > 0 && (
177+
<ul
178+
ref={suggestionsRef}
179+
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]"
180+
>
181+
{suggestions.map((s, idx) => (
182+
<li
183+
key={idx}
184+
onClick={() => handleSelectSubject(s)}
185+
className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
186+
>
187+
{s}
188+
</li>
189+
))}
190+
</ul>
191+
)}
192+
</div>
193+
194+
<div className="mb-8 flex justify-center gap-4">
195+
<Select
196+
onValueChange={setSelectedExam}
197+
disabled={!selectedSubject}
198+
value={selectedExam ?? undefined}
199+
>
200+
<SelectTrigger className="w-32">
201+
<SelectValue placeholder="Exam" />
202+
</SelectTrigger>
203+
<SelectContent>
204+
{exams.map((exam) => (
205+
<SelectItem key={exam} value={exam}>
206+
{exam}
207+
</SelectItem>
208+
))}
209+
</SelectContent>
210+
</Select>
211+
<Select
212+
onValueChange={setSelectedSlot}
213+
disabled={!selectedSubject}
214+
value={selectedSlot ?? undefined}
215+
>
216+
<SelectTrigger className="w-32">
217+
<SelectValue placeholder="Slot" />
218+
</SelectTrigger>
219+
<SelectContent>
220+
{slots.map((slot) => (
221+
<SelectItem key={slot} value={slot}>
222+
{slot}
223+
</SelectItem>
224+
))}
225+
</SelectContent>
226+
</Select>
227+
<Select
228+
onValueChange={setSelectedYear}
229+
disabled={!selectedSubject}
230+
value={selectedYear ?? undefined}
231+
>
232+
<SelectTrigger className="w-32">
233+
<SelectValue placeholder="Year" />
234+
</SelectTrigger>
235+
<SelectContent>
236+
{[...years]
237+
.sort((a, b) => Number(b) - Number(a))
238+
.map((year) => (
239+
<SelectItem key={year} value={year}>
240+
{year}
241+
</SelectItem>
242+
))}
243+
</SelectContent>
244+
</Select>
245+
</div>
246+
247+
<Button
248+
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]"
249+
onClick={handleSubmit}
250+
>
251+
Submit
252+
</Button>
253+
</div>
254+
255+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
256+
{isLoading ? (
257+
<SkeletonPaperCard length={4} />
258+
) : (
259+
displayPapers.map((paper, subIndex) => (
260+
<div key={subIndex} className="h-full">
261+
<UpcomingPaper subject={paper.subject} slots={paper.slots} />
262+
</div>
263+
))
264+
)}
265+
</div>
266+
</main>
267+
</div>
268+
);
5269
}

src/app/upload/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,7 @@ const Page = () => {
140140
await toast.promise(
141141
async () => {
142142
try {
143-
console.log("this is happening now");
144143
await axios.post<APIResponse>("/api/upload", formData);
145-
console.log("this is happening after now");
146144
return { message: "Papers uploaded successfully!" };
147145
} catch (error) {
148146
if (error instanceof AxiosError && error.response?.data) {

src/components/CatalogueContent.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,17 +323,17 @@ const CatalogueContent = () => {
323323

324324
<div className="flex items-center gap-2 p-7">
325325
<div>
326-
<p className="text-s font-semibold text-white/80">
326+
<p className="text-s font-semibold text-gray-700 dark:text-white/80">
327327
{subject?.split("[")[1]?.replace("]", "")}
328328
</p>
329-
<h2 className="text-2xl font-extrabold text-white md:text-3xl">
329+
<h2 className="text-2xl font-extrabold text-gray-700 dark:text-white md:text-3xl">
330330
{subject?.split(" [")[0]}
331331
</h2>
332332
</div>
333333
<div className="mt-7">
334334
<button onClick={handlePinToggle}>
335335
<Pin
336-
className={`h-7 w-7 ${pinned ? "fill-[#A78BFA]" : ""} stroke-white`}
336+
className={`h-7 w-7 ${pinned ? "fill-[#A78BFA]" : ""} stroke-gray-700 dark:stroke-white`}
337337
/>
338338
</button>
339339
</div>

0 commit comments

Comments
 (0)