diff --git a/src/app/actions/get-papers-by-id.ts b/src/app/actions/get-papers-by-id.ts index 3f30051..6909aa6 100644 --- a/src/app/actions/get-papers-by-id.ts +++ b/src/app/actions/get-papers-by-id.ts @@ -1,20 +1,22 @@ -import { type PaperResponse } from "@/interface"; +import type { PaperResponse, ApiResponse } from "@/interface"; import axios, { type AxiosResponse } from "axios"; export const fetchPaperID = async (id: string): Promise => { const serverUrl = process.env.SERVER_URL ?? "https://papers.codechefvit.com"; try { - const response: AxiosResponse = await axios.get( + const response: AxiosResponse> = await axios.get( `${serverUrl}/api/paper-by-id/${id}`, ); - return response.data; + + if (!response.data.data) { + throw new Error("Paper not found"); + } + return response.data.data; } catch (err: unknown) { - if (axios.isAxiosError(err)) { - console.error("Axios error:", err.response?.data ?? err.message); - const errorMessage = - (err.response?.data as { message?: string })?.message ?? - "Failed to fetch paper"; + if (axios.isAxiosError>(err)) { + const errorMessage = err.response?.data?.message ?? err.message; + console.error("Axios error:", errorMessage); throw new Error(errorMessage); } else { console.error("Unexpected error:", err); diff --git a/src/app/api/course-list/route.ts b/src/app/api/course-list/route.ts index 47bf19d..f84f51f 100644 --- a/src/app/api/course-list/route.ts +++ b/src/app/api/course-list/route.ts @@ -1,17 +1,14 @@ -import { NextResponse } from "next/server"; import { getCourseList } from "@/lib/services/subject"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; export async function GET() { try { const courses = await getCourseList(); - return NextResponse.json(courses, { status: 200 }); + return success(courses); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to fetch courses", error }, - { status: 500 }, - ); + return failure("Failed to fetch courses", 500); } } diff --git a/src/app/api/paper-by-id/[id]/route.ts b/src/app/api/paper-by-id/[id]/route.ts index 5a69c28..243754e 100644 --- a/src/app/api/paper-by-id/[id]/route.ts +++ b/src/app/api/paper-by-id/[id]/route.ts @@ -1,23 +1,19 @@ -import { NextResponse } from "next/server"; import { Types } from "mongoose"; import { getPaperById } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response" export async function GET(req: Request, { params }: { params: { id: string } }) { try { const { id } = params; if (!Types.ObjectId.isValid(id)) { - return NextResponse.json({ message: "Invalid paper ID" }, { status: 400 }); + return failure("Invalid paper ID"); } - const paper = await getPaperById(id); - - return NextResponse.json(paper, { status: 200 }); + + return success(paper); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to fetch paper", error }, - { status: 500 }, - ); + return failure("Failed to fetch paper", 500); } } diff --git a/src/app/api/papers/count/route.ts b/src/app/api/papers/count/route.ts index cc2fbf1..bdddfea 100644 --- a/src/app/api/papers/count/route.ts +++ b/src/app/api/papers/count/route.ts @@ -1,5 +1,5 @@ -import { NextResponse } from "next/server"; import { getCourseCounts } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -7,11 +7,9 @@ export async function GET(req: Request) { try { const courseCount = await getCourseCounts(); - return NextResponse.json(courseCount, { status: 200 }); + return success(courseCount); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch course counts", error }, - { status: 500 }, - ); + console.error(error); + return failure("Failed to fetch course counts", 500); } } diff --git a/src/app/api/papers/route.ts b/src/app/api/papers/route.ts index b7840dd..a5eb23c 100644 --- a/src/app/api/papers/route.ts +++ b/src/app/api/papers/route.ts @@ -1,5 +1,6 @@ -import { NextResponse, type NextRequest } from "next/server"; +import { type NextRequest } from "next/server"; import { getPapersBySubject } from "@/lib/services/paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -8,18 +9,13 @@ export async function GET(req: NextRequest) { const url = req.nextUrl.searchParams; const sub = url.get("subject"); if (!sub) { - return NextResponse.json( - { message: "Subject query parameter is required" }, - { status: 400 }, - ); + return failure("Subject query parameter is required"); } const paper = await getPapersBySubject(sub); - return NextResponse.json(paper, { status: 200 }); + return success(paper); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch papers", error }, - { status: 500 }, - ); + console.error(error); + return failure("Failed to fetch papers", 500); } } diff --git a/src/app/api/related-subject/route.ts b/src/app/api/related-subject/route.ts index 6f374ff..debd00d 100644 --- a/src/app/api/related-subject/route.ts +++ b/src/app/api/related-subject/route.ts @@ -1,5 +1,6 @@ import { getRelatedSubjects } from "@/lib/services/subject"; -import { NextResponse, type NextRequest } from "next/server"; +import { type NextRequest } from "next/server"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -9,21 +10,13 @@ export async function GET(req: NextRequest) { const subject = url.get("subject"); if (!subject) { - return NextResponse.json( - { message: "Subject query parameter is required" }, - { status: 400 }, - ); + return failure("Subject query parameter is required", 400); } const relatedSubjects = await getRelatedSubjects(subject); - return NextResponse.json( - {related_subjects: relatedSubjects}, - { status: 200 }, - ); + return success({ related_subjects: relatedSubjects }); } catch (error) { - return NextResponse.json( - { message: "Failed to fetch related subject", error }, - { status: 500 }, - ); + console.error(error); + return failure("Failed to fetch related subject", 500); } } \ No newline at end of file diff --git a/src/app/api/report-tag/route.ts b/src/app/api/report-tag/route.ts index 032cbd1..d709e18 100644 --- a/src/app/api/report-tag/route.ts +++ b/src/app/api/report-tag/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { reportTag, ReportTagBody } from "@/lib/services/report"; import { rateLimitCheck } from "@/lib/utils/rate-limiter"; +import { success, failure } from "@/lib/utils/response"; import { customErrorHandler } from "@/lib/utils/error"; export async function POST(req: Request & { ip?: string }) { @@ -9,18 +9,12 @@ export async function POST(req: Request & { ip?: string }) { const paperId = typeof body.paperId === "string" ? body.paperId : undefined; if (!paperId) { - return NextResponse.json( - { error: "paperId is required" }, - { status: 400 } - ); + return failure("paperId is required", 400); } await rateLimitCheck(req, paperId); const newReport = await reportTag(paperId, body); - return NextResponse.json( - { message: "Report submitted.", report: newReport }, - { status: 201 } - ); + return success({ message: "Report submitted.", report: newReport }, "Created", 201); } catch (err) { console.error(err); return customErrorHandler(err, "Failed to submit tag report."); diff --git a/src/app/api/request/route.ts b/src/app/api/request/route.ts index 48c9302..104b25c 100644 --- a/src/app/api/request/route.ts +++ b/src/app/api/request/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import PaperRequest from "@/db/paperRequest"; +import { success, failure } from "@/lib/utils/response"; export async function POST(req: Request) { try { @@ -15,22 +15,13 @@ export async function POST(req: Request) { const { subject, exam, slot, year } = body; if (!subject || !exam || !slot || !year) { - return NextResponse.json( - { error: "All fields are required." }, - { status: 400 }, - ); + return failure("All fields are required.", 400); } const newRequest = await PaperRequest.create({ subject, exam, slot, year }); - return NextResponse.json( - { message: "Paper request submitted successfully!", request: newRequest }, - { status: 201 }, - ); + return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201); } catch (error) { console.error("Error creating paper request:", error); - return NextResponse.json( - { error: "Failed to submit request." }, - { status: 500 }, - ); + return failure("Failed to submit request.", 500); } } diff --git a/src/app/api/selected-papers/route.ts b/src/app/api/selected-papers/route.ts index 5c822f6..ba02d77 100644 --- a/src/app/api/selected-papers/route.ts +++ b/src/app/api/selected-papers/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -11,23 +11,11 @@ export async function GET() { const selectedPapers = await Paper.find({ isSelected: true }).limit(8); if (selectedPapers.length === 0) { - return NextResponse.json( - { - message: "No selected papers found.", - }, - { status: 404 }, - ); + return failure("No selected papers found.", 404); } - return NextResponse.json(selectedPapers, { - status: 200, - }); + return success(selectedPapers); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/app/api/subscribe/route.ts b/src/app/api/subscribe/route.ts index ce22818..791e7d3 100644 --- a/src/app/api/subscribe/route.ts +++ b/src/app/api/subscribe/route.ts @@ -1,19 +1,19 @@ -import { NextResponse } from "next/server"; -import { customErrorHandler } from "@/lib/utils/error"; import { subscribeEmail } from "@/lib/services/subscribe"; +import { success, failure } from "@/lib/utils/response"; +import { customErrorHandler } from "@/lib/utils/error"; export async function POST(req: Request) { try { const { email } = (await req.json()) as { email: string }; if (!email) { - return NextResponse.json({ error: "Email is required" }, { status: 400 }); + return failure("Email is required", 400); } await subscribeEmail(email); - return NextResponse.json({ message: "Email added successfully" }); + return success({ message: "Email added successfully" }); } catch (error) { console.error("Error adding email:", error); return customErrorHandler(error, "Failed to add email"); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/app/api/upcoming-papers/route.ts b/src/app/api/upcoming-papers/route.ts index 9c76428..1ba624f 100644 --- a/src/app/api/upcoming-papers/route.ts +++ b/src/app/api/upcoming-papers/route.ts @@ -1,6 +1,6 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import UpcomingSubject from "@/db/upcoming-paper"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -13,24 +13,12 @@ export async function GET() { .lean(); if (selectedSubjects.length === 0) { - return NextResponse.json( - { - message: "No selected papers found.", - }, - { status: 404 }, - ); + return failure("No selected papers found.", 404); } - return NextResponse.json(selectedSubjects, { - status: 200, - }); + return success(selectedSubjects); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/app/api/upload/route.ts b/src/app/api/upload/route.ts index 2fad98e..6ce5806 100644 --- a/src/app/api/upload/route.ts +++ b/src/app/api/upload/route.ts @@ -1,8 +1,8 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import { PaperAdmin } from "@/db/papers"; import { createPDFfromImages } from "@/lib/storage/pdf"; import { uploadPDF, uploadThumbnail } from "@/lib/storage/gcp"; +import { success, failure } from "@/lib/utils/response"; export const runtime = "nodejs"; @@ -15,19 +15,13 @@ export async function POST(req: Request) { const thumb = formData.get("thumbnail") as File | null; if (files.length === 0) { - return NextResponse.json( - { error: "No files received." }, - { status: 400 }, - ); + return failure("No files received.", 400); } let pdfBytes: Uint8Array; if (isPdf) { if (!files[0]) { - return NextResponse.json( - { error: "No PDF file provided." }, - { status: 400 }, - ); + return failure("No PDF file provided.", 400); } pdfBytes = new Uint8Array(await files[0].arrayBuffer()); } else { @@ -57,15 +51,9 @@ export async function POST(req: Request) { }); await paper.save(); - return NextResponse.json( - { status: "success", file_url, thumbnail_url }, - { status: 201 }, - ); + return success({ file_url, thumbnail_url }, "Created", 201); } catch (error) { console.error(error); - return NextResponse.json( - { message: "Failed to upload papers", error }, - { status: 500 }, - ); + return failure("Failed to upload papers", 500); } } diff --git a/src/app/api/user-papers/route.ts b/src/app/api/user-papers/route.ts index 8d2b9df..939d29f 100644 --- a/src/app/api/user-papers/route.ts +++ b/src/app/api/user-papers/route.ts @@ -1,8 +1,8 @@ -import { NextResponse } from "next/server"; import { connectToDatabase } from "@/lib/database/mongoose"; import Paper from "@/db/papers"; import { StoredSubjects } from "@/interface"; import { transformPapersToSubjectSlots } from "@/lib/services/paper-transform"; +import { success, failure } from "@/lib/utils/response"; export const dynamic = "force-dynamic"; @@ -19,16 +19,9 @@ export async function POST(req: Request) { const transformedPapers = transformPapersToSubjectSlots(usersPapers); - return NextResponse.json(transformedPapers, { - status: 200, - }); + return success(transformedPapers); } catch (error) { console.error("Error fetching papers:", error); - return NextResponse.json( - { - error: "Failed to fetch papers.", - }, - { status: 500 }, - ); + return failure("Failed to fetch papers.", 500); } } diff --git a/src/app/request/page.tsx b/src/app/request/page.tsx index 97367f6..264370c 100644 --- a/src/app/request/page.tsx +++ b/src/app/request/page.tsx @@ -20,6 +20,7 @@ import toast from "react-hot-toast"; import { Search } from "lucide-react"; import SkeletonPaperCard from "@/components/SkeletonPaperCard"; import { useCourses } from "@/context/courseContext"; +import { type ApiResponse } from '@/interface' type Course = { name?: string | null; @@ -48,11 +49,9 @@ export default function PaperRequest() { async function fetchPapers() { try { setIsLoading(true); - const response = await axios.get( - "/api/upcoming-papers", - ); + const response = await axios.get>("/api/upcoming-papers"); - setDisplayPapers(response.data); + setDisplayPapers(response.data.data ?? []); } catch (error) { console.error("Failed to fetch papers:", error); } finally { diff --git a/src/app/upload/page.tsx b/src/app/upload/page.tsx index 18533bb..cab0010 100644 --- a/src/app/upload/page.tsx +++ b/src/app/upload/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useCallback, useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; import toast from "react-hot-toast"; import { Button } from "@/components/ui/button"; import axios, { AxiosError } from "axios"; @@ -13,7 +13,7 @@ import { TouchSensor, useSensor, useSensors, - DragEndEvent, + type DragEndEvent, } from "@dnd-kit/core"; import { arrayMove, @@ -24,16 +24,16 @@ import { import { CSS } from "@dnd-kit/utilities"; import Dropzone from "react-dropzone"; import { Upload, XIcon } from "lucide-react"; -import { getDocument, GlobalWorkerOptions } from "pdfjs-dist"; +import { GlobalWorkerOptions } from "pdfjs-dist"; +import type { ApiResponse } from "@/interface" GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.8.69/pdf.worker.min.mjs"; -interface APIResponse { - status: string; - message?: string; +interface uploadResponse { + file_url: string; + thumbnail_url: string; } - export default function Page() { const campus = "Vellore"; const [files, setFiles] = useState([]); @@ -44,6 +44,12 @@ export default function Page() { const [isGlobalDragging, setIsGlobalDragging] = useState(false); const [zoomIndex, setZoomIndex] = useState(null); + const previewsRef = useRef(previews); + + useEffect(() => { + previewsRef.current = previews; + }, [previews]); + useEffect(() => { const onDragEnter = () => setIsGlobalDragging(true); const onDragLeave = () => setIsGlobalDragging(false); @@ -63,7 +69,7 @@ export default function Page() { // Cleanup previews on unmount useEffect(() => { return () => { - previews.forEach((item) => { + previewsRef.current.forEach((item) => { try { URL.revokeObjectURL(item.preview); } catch {} @@ -273,13 +279,13 @@ export default function Page() { await toast.promise( async () => { try { - await axios.post("/api/upload", formData); + await axios.post>("/api/upload", formData); return { message: "Papers uploaded successfully!" }; } catch (error) { - if (error instanceof AxiosError && error.response?.data) { - const errorData = error.response.data as APIResponse; + if (error instanceof AxiosError) { + const errorData = error?.response?.data as ApiResponse; const errorMessage = - errorData.message ?? "Failed to upload papers"; + errorData?.message ?? "Failed to upload papers"; throw new Error(errorMessage); } throw new Error("Failed to upload papers"); diff --git a/src/components/CatalogueContent.tsx b/src/components/CatalogueContent.tsx index df84ff6..f99d245 100644 --- a/src/components/CatalogueContent.tsx +++ b/src/components/CatalogueContent.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import axios, { type AxiosError } from "axios"; import { Button } from "@/components/ui/button"; -import { type IPaper, type Filters, type StoredSubjects } from "@/interface"; +import { type IPaper, type Filters, type StoredSubjects, type ApiResponse } from "@/interface"; import Card from "./Card"; import Loader from "./ui/loader"; import SideBar from "../components/SideBar"; @@ -71,13 +71,10 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { if (!subject) return; const fetchRelatedSubjects = async () => { try { - const res = await axios.get<{ related_subjects: string[] }>( - "/api/related-subject", - { - params: { subject }, - }, - ); - const data = res.data.related_subjects; + const res = await axios.get>("/api/related-subject", { + params: { subject }, + }); + const data = res.data.data?.related_subjects; if (data && data.length > 0) { setRelatedSubjects(data); } else { @@ -153,20 +150,23 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => { const fetchPapers = async () => { setLoading(true); try { - const papersResponse = await axios.get("/api/papers", { - params: { subject }, - }); - const data: Filters = papersResponse.data; - const papersData = data.papers; + const papersResponse = await axios.get>( + "/api/papers", + { + params: { subject }, + }, + ); + + const data = papersResponse.data.data!; + const papersData = data?.papers ?? []; setFilterOptions(data); setPapers(papersData); } catch (error) { setPapers([]); - const axiosError = error as AxiosError; + setError( - axios.isAxiosError(axiosError) - ? ((axiosError.response?.data as { message?: string })?.message ?? - "Error fetching papers") + axios.isAxiosError>(error) + ? (error.response?.data.message ?? "Error fetching papers") : "Error fetching papers", ); } finally { diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index c59b7b4..464a542 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -15,6 +15,7 @@ import { } from "react-icons/fa6"; import { Bold, Mail } from "lucide-react"; import toast from "react-hot-toast"; +import type { ApiResponse } from '@/interface' type SubscribeResponse = { success?: boolean; @@ -43,7 +44,7 @@ export default function Footer() { body: JSON.stringify({ email }), }) .then(async (res) => { - const data = (await res.json()) as SubscribeResponse; + const data = (await res.json()) as ApiResponse; if (!res.ok) throw new Error(data.message ?? "Something went wrong."); return data; }), diff --git a/src/components/PapersCarousel.tsx b/src/components/PapersCarousel.tsx index 3fd4a01..b2368c5 100644 --- a/src/components/PapersCarousel.tsx +++ b/src/components/PapersCarousel.tsx @@ -15,6 +15,7 @@ import Autoplay from "embla-carousel-autoplay"; import { chunkArray } from "@/lib/utils/array"; import { Skeleton } from "@/components/ui/skeleton"; import SkeletonPaperCard from "@/components/SkeletonPaperCard"; +import { type ApiResponse } from "@/interface" function PapersCarousel() { const [displayPapers, setDisplayPapers] = useState([]); @@ -41,10 +42,8 @@ function PapersCarousel() { const fetchPapers = async () => { try { setIsLoading(true); - const response = await axios.get( - "/api/upcoming-papers", - ); - setDisplayPapers(response.data); + const response = await axios.get>("/api/upcoming-papers"); + setDisplayPapers(response.data.data ?? []); } catch (error) { console.error("Failed to fetch papers:", error); } finally { diff --git a/src/components/PinnedPapersCarousel.tsx b/src/components/PinnedPapersCarousel.tsx index ec90d28..be9f72e 100644 --- a/src/components/PinnedPapersCarousel.tsx +++ b/src/components/PinnedPapersCarousel.tsx @@ -16,6 +16,12 @@ import { chunkArray } from "@/lib/utils/array"; import { type StoredSubjects } from "@/interface"; import SkeletonPaperCard from "./SkeletonPaperCard"; import PinnedModal from "./ui/PinnedModal"; +import { type ApiResponse } from "@/interface" + +interface userPaperResponse { + subject: string; + slots: string[] +} function PinnedPapersCarousel() { const [isLoading, setIsLoading] = useState(true); @@ -61,12 +67,12 @@ function PinnedPapersCarousel() { localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post<{ subject: string; slots: string[] }[]>( + const response = await axios.post>( "/api/user-papers", storedSubjects, ); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), @@ -114,11 +120,9 @@ function PinnedPapersCarousel() { localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post< - { subject: string; slots: string[] }[] - >("/api/user-papers", storedSubjects); + const response = await axios.post>("/api/user-papers", storedSubjects); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), diff --git a/src/components/RelatedPaper.tsx b/src/components/RelatedPaper.tsx index 447be7c..7085be8 100644 --- a/src/components/RelatedPaper.tsx +++ b/src/components/RelatedPaper.tsx @@ -8,6 +8,7 @@ import { type IPaper, type Filters } from "@/interface"; import Card from "@/components/Card"; import Loader from "@/components/ui/loader"; import { Button } from "./ui/button"; +import { type ApiResponse } from "@/interface" const RelatedPapers = () => { const params = useParams(); @@ -20,15 +21,21 @@ const RelatedPapers = () => { useEffect(() => { const fetchData = async () => { try { - const getpaper = await axios.get(`/api/paper-by-id/${id}`); - const paper = getpaper.data; + const getpaper = await axios.get>(`/api/paper-by-id/${id}`); + if (!getpaper.data.data) { + throw new Error("Paper not found"); + } + const paper = getpaper.data.data; setCurrentPaper(paper); - const allPapersBySubject = await axios.get("/api/papers", { - params: { subject: paper.subject }, - }); + const allPapersBySubject = await axios.get>( + "/api/papers", + { + params: { subject: paper.subject }, + }, + ); - const all = allPapersBySubject.data.papers; + const all = allPapersBySubject.data.data?.papers ?? []; const sameExam = all .filter((p) => p._id !== paper._id && p.exam === paper.exam) diff --git a/src/components/ReportTagModal.tsx b/src/components/ReportTagModal.tsx index 91c31e2..a04e694 100644 --- a/src/components/ReportTagModal.tsx +++ b/src/components/ReportTagModal.tsx @@ -16,8 +16,9 @@ import LabeledInput from "@/components/ui/LabeledInput"; import LabeledSelect from "@/components/ui/LabeledSelect"; import axios, { AxiosError } from "axios"; import toast from "react-hot-toast"; +import { type ApiResponse } from '@/interface' -type ReportResponse = { error?: string; message?: string }; +type ReportResponse = ApiResponse<{ error?: string; message?: string }>; interface ReportTagModalProps { paperId: string; diff --git a/src/components/ui/PinnedModal.tsx b/src/components/ui/PinnedModal.tsx index b977941..a341965 100644 --- a/src/components/ui/PinnedModal.tsx +++ b/src/components/ui/PinnedModal.tsx @@ -27,8 +27,16 @@ import { verticalListSortingStrategy, arrayMove, } from "@dnd-kit/sortable"; + +import { type ApiResponse } from '@/interface' + import { CSS } from "@dnd-kit/utilities"; +interface userPaperResponse { + subject: string; + slots: string[] +} + const SortableItem = ({ id, subject, @@ -97,11 +105,11 @@ const PinnedModal = ({ const storedSubjects = JSON.parse( localStorage.getItem("userSubjects") ?? "[]", ) as StoredSubjects; - const response = await axios.post<{ subject: string; slots: string[] }[]>( + const response = await axios.post>( "/api/user-papers", storedSubjects, ); - const fetchedPapers = response.data; + const fetchedPapers = response.data.data!; const fetchedSubjectsSet = new Set( fetchedPapers.map((paper) => paper.subject), ); diff --git a/src/components/ui/RequestModal.tsx b/src/components/ui/RequestModal.tsx index 23339e8..c2dfa92 100644 --- a/src/components/ui/RequestModal.tsx +++ b/src/components/ui/RequestModal.tsx @@ -78,15 +78,15 @@ const RequestModal = ({section = "navbar"} : {section? : string}) => { try { await toast.promise( axios.post("/api/request", { - subject: selectedSubject, - exam: selectedExam, - slot: selectedSlot, - year: selectedYear, + 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.", + loading: "Submitting your request...", + success: "Your paper request was submitted successfully", + error: "Failed to submit your request. Please try again later.", }, ); diff --git a/src/context/courseContext.tsx b/src/context/courseContext.tsx index aef5386..8c873b3 100644 --- a/src/context/courseContext.tsx +++ b/src/context/courseContext.tsx @@ -8,6 +8,8 @@ import { type ICourseWithCount, } from "@/interface"; +import { type ApiResponse } from '@/interface' + interface CoursesContextType { courses: ICourseWithCount[]; loading: boolean; @@ -30,19 +32,17 @@ export function CoursesProvider({ children }: { children: React.ReactNode }) { setLoading(true); setError(null); try { - const res = await axios.get("/api/course-list", { + const res = await axios.get>("/api/course-list", { headers: { "Cache-Control": "no-cache" }, }); - - const countRes = await axios.get("/api/papers/count", { + const countRes = await axios.get>("/api/papers/count", { headers: { "Cache-Control": "no-cache" }, }); - const countMap = new Map( - countRes.data.map((c) => [c.name, c.count]), + (countRes.data.data ?? []).map((c) => [c.name, c.count]), ); - const mergedCourses: ICourseWithCount[] = res.data.map((course) => ({ + const mergedCourses: ICourseWithCount[] = (res.data.data ?? []).map((course) => ({ _id: course._id, name: course.name, count: countMap.get(course.name) ?? 0, @@ -50,7 +50,7 @@ export function CoursesProvider({ children }: { children: React.ReactNode }) { setCourses(mergedCourses); } catch (err: unknown) { - if (axios.isAxiosError(err)) { + if (axios.isAxiosError>(err)) { setError(err.response?.data?.message ?? err.message); } else if (err instanceof Error) { setError(err.message); diff --git a/src/interface.ts b/src/interface.ts index 272ea56..016128f 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -52,6 +52,12 @@ export interface APIResponse { status: number; } +export interface ApiResponse { + status: "success" | "error"; + data: T | null; + message: string; +} + export interface ConverttoPDFResponse { url: string; secure_url: string; diff --git a/src/lib/utils/error.ts b/src/lib/utils/error.ts index f4b1890..7340d70 100644 --- a/src/lib/utils/error.ts +++ b/src/lib/utils/error.ts @@ -1,4 +1,4 @@ -import { NextResponse } from "next/server"; +import { failure } from "@/lib/utils/response" export class CustomError extends Error { status: number; @@ -12,14 +12,8 @@ export class CustomError extends Error { export function customErrorHandler(error: unknown, defaultMessage: string) { if (error instanceof CustomError) { - return NextResponse.json( - {message: error.message}, - {status: error.status} - ); + return failure(error.message, error.status); } else { - return NextResponse.json( - {message: defaultMessage}, - {status: 500} - ); + return failure(defaultMessage, 500) } } diff --git a/src/lib/utils/response.ts b/src/lib/utils/response.ts new file mode 100644 index 0000000..4110509 --- /dev/null +++ b/src/lib/utils/response.ts @@ -0,0 +1,21 @@ +import { NextResponse } from "next/server" +import { ApiResponse } from '@/interface' + +export const success = ( + data: T, + message = "OK", + status = 200 +) => + NextResponse.json>( + { status: "success", data, message }, + { status } + ); + +export const failure = ( + message: string, + status = 400, +) => + NextResponse.json>( + { status: "error", data: null, message}, + { status } + ) \ No newline at end of file diff --git a/src/middleware.ts b/src/middleware.ts index 6e5fa64..42cd036 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -4,6 +4,7 @@ import { paperRequestRatelimit, subscribeRatelimit, } from "./lib/utils/ratelimit"; +import { failure } from "@/lib/utils/response" export const config = { matcher: ["/api/upload", "/api/request", "/api/subscribe"], @@ -16,30 +17,21 @@ export default async function middleware(request: NextRequest) { if (pathname === "/api/upload") { const { success } = await aiUploadRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "You can upload a maximum of 5 papers every 15 minutes" }, - { status: 429 }, - ); + return failure("You can upload a maximum of 5 papers every 15 minutes", 429); } } if (pathname === "/api/request") { const { success } = await paperRequestRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "You can submit a maximum of 5 requests every 15 minutes" }, - { status: 429 }, - ); + return failure("You can submit a maximum of 5 requests every 15 minutes", 429); } } if (pathname === "/api/subscribe") { const { success } = await subscribeRatelimit.limit(ip); if (!success) { - return NextResponse.json( - { message: "Maximum of 3 newsletter subscriptions per hour" }, - { status: 429 }, - ); + return failure("Maximum of 3 newsletter subscriptions per hour", 429); } }