Skip to content

Commit cebf008

Browse files
refactor
1 parent 5724e11 commit cebf008

File tree

12 files changed

+313
-396
lines changed

12 files changed

+313
-396
lines changed

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: 263 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,265 @@
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 axios.post("/api/request", {
131+
subject: selectedSubject,
132+
exam: selectedExam,
133+
slot: selectedSlot,
134+
year: selectedYear,
135+
});
136+
137+
alert("✅ Your paper request was submitted successfully 🎉");
138+
139+
setSearchText("");
140+
setSelectedSubject(null);
141+
setSelectedExam(null);
142+
setSelectedSlot(null);
143+
setSelectedYear(null);
144+
} catch (error) {
145+
console.error("Error submitting request:", error);
146+
alert("❌ Failed to submit your request. Please try again later.");
147+
}
148+
};
149+
150+
return (
151+
<div className="min-h-screen bg-[#F3F5FF] px-6 py-12 text-black dark:bg-[#070114] dark:text-white">
152+
<main>
153+
<div className="mx-auto mb-16 max-w-4xl text-center">
154+
<h2 className="mb-12 font-vipnabd text-3xl font-extrabold md:text-4xl">
155+
Specific Paper Request
156+
</h2>
157+
158+
<div className="relative mx-auto mb-8 max-w-xl font-play">
159+
<Input
160+
type="text"
161+
value={searchText}
162+
onChange={(e) => setSearchText(e.target.value)}
163+
placeholder="Search by subject..."
164+
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" : ""}`}
165+
/>
166+
<button
167+
type="button"
168+
className="absolute inset-y-0 right-0 flex items-center pr-3"
169+
>
170+
<Search className="h-5 w-5 text-black dark:text-white" />{" "}
171+
</button>
172+
{suggestions.length > 0 && (
173+
<ul
174+
ref={suggestionsRef}
175+
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]"
176+
>
177+
{suggestions.map((s, idx) => (
178+
<li
179+
key={idx}
180+
onClick={() => handleSelectSubject(s)}
181+
className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
182+
>
183+
{s}
184+
</li>
185+
))}
186+
</ul>
187+
)}
188+
</div>
189+
190+
<div className="mb-8 flex justify-center gap-4">
191+
<Select
192+
onValueChange={setSelectedExam}
193+
disabled={!selectedSubject}
194+
value={selectedExam ?? undefined}
195+
>
196+
<SelectTrigger className="w-32">
197+
<SelectValue placeholder="Exam" />
198+
</SelectTrigger>
199+
<SelectContent>
200+
{exams.map((exam) => (
201+
<SelectItem key={exam} value={exam}>
202+
{exam}
203+
</SelectItem>
204+
))}
205+
</SelectContent>
206+
</Select>
207+
<Select
208+
onValueChange={setSelectedSlot}
209+
disabled={!selectedSubject}
210+
value={selectedSlot ?? undefined}
211+
>
212+
<SelectTrigger className="w-32">
213+
<SelectValue placeholder="Slot" />
214+
</SelectTrigger>
215+
<SelectContent>
216+
{slots.map((slot) => (
217+
<SelectItem key={slot} value={slot}>
218+
{slot}
219+
</SelectItem>
220+
))}
221+
</SelectContent>
222+
</Select>
223+
<Select
224+
onValueChange={setSelectedYear}
225+
disabled={!selectedSubject}
226+
value={selectedYear ?? undefined}
227+
>
228+
<SelectTrigger className="w-32">
229+
<SelectValue placeholder="Year" />
230+
</SelectTrigger>
231+
<SelectContent>
232+
{[...years]
233+
.sort((a, b) => Number(b) - Number(a))
234+
.map((year) => (
235+
<SelectItem key={year} value={year}>
236+
{year}
237+
</SelectItem>
238+
))}
239+
</SelectContent>
240+
</Select>
241+
</div>
242+
243+
<Button
244+
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]"
245+
onClick={handleSubmit}
246+
>
247+
Submit
248+
</Button>
249+
</div>
250+
251+
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-4">
252+
{isLoading ? (
253+
<SkeletonPaperCard length={4} />
254+
) : (
255+
displayPapers.map((paper, subIndex) => (
256+
<div key={subIndex} className="h-full">
257+
<UpcomingPaper subject={paper.subject} slots={paper.slots} />
258+
</div>
259+
))
260+
)}
261+
</div>
262+
</main>
263+
</div>
264+
);
5265
}

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/Footer.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,17 +96,17 @@ export default function Footer() {
9696
{/* Events */}
9797
<div className="flex w-full flex-col gap-2 text-black dark:text-white sm:w-[45%] lg:w-[15%]">
9898
<h3 className="font-jost text-xl font-semibold">Events</h3>
99-
<Link href="https://devsoc25.codechefvit.com">DevSoc</Link>
100-
<Link href="https://gravitas.codechefvit.com">CookOff</Link>
101-
<Link href="https://gravitas.codechefvit.com">Clueminati</Link>
99+
<Link href="https://devsoc25.codechefvit.com" target="_blank">DevSoc</Link>
100+
<Link href="https://gravitas.codechefvit.com" target="_blank">CookOff</Link>
101+
<Link href="https://gravitas.codechefvit.com" target="_blank">Clueminati</Link>
102102
</div>
103103

104104
{/* Projects */}
105105
<div className="flex w-full flex-col gap-2 text-black dark:text-white sm:w-[45%] lg:w-[20%]">
106106
<h3 className="font-jost text-xl font-semibold">Our Projects</h3>
107-
<Link href="https://papers.codechefvit.com">Papers</Link>
108-
<Link href="https://contactify.codechefvit.com">Contactify</Link>
109-
<Link href="https://ffcs.codechefvit.com">FFCS-inator</Link>
107+
<Link href="https://papers.codechefvit.com" target="_blank">Papers</Link>
108+
<Link href="https://contactify.codechefvit.com" target="_blank">Contactify</Link>
109+
<Link href="https://ffcs.codechefvit.com" target="_blank">FFCS-inator</Link>
110110
</div>
111111

112112
{/* Suggestions */}

src/components/Navbar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ function Navbar() {
4040
<div className="sticky top-0 z-10 w-full bg-[#B2B8FF] px-4 py-4 dark:bg-[#130E1F] md:px-8 md:py-5">
4141
<div className="flex items-center justify-between">
4242
<div className="flex items-center gap-4">
43-
<a href="https://www.codechefvit.com/">
43+
<Link href="https://www.codechefvit.com/" target="_blank">
4444
<Image
4545
src={ccLogo as HTMLImageElement}
4646
alt="codechef-logo"
4747
height={60}
4848
width={60}
4949
/>
50-
</a>
50+
</Link>
5151

5252
<Link
5353
href="/"

0 commit comments

Comments
 (0)