Skip to content

Commit a0527d1

Browse files
Merge pull request #361 from karannfr/prod
Paper Request UX Updated
2 parents 416cbc7 + 968e338 commit a0527d1

6 files changed

Lines changed: 253 additions & 33 deletions

File tree

src/components/FloatingNavbar.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
DropdownMenuItem,
1919
} from "@/components/ui/dropdown-menu";
2020
import PinnedModal from "./ui/PinnedModal";
21+
import RequestModal from "./ui/RequestModal";
22+
2123
interface Props {
2224
onNavigate: () => void;
2325
}
@@ -66,15 +68,10 @@ export default function FloatingNavbar({ onNavigate }: Props) {
6668
</div>
6769
</DropdownMenuItem>
6870

69-
<DropdownMenuItem asChild>
70-
<Link
71-
href="/request"
72-
onClick={() => onNavigate()}
73-
className="flex w-full items-center gap-3 rounded-lg px-3 py-1 hover:bg-[#1A1823] transition"
74-
>
75-
<ArrowUpRight className="h-4 w-4" />
76-
<span className="text-sm font-medium">Paper Request</span>
77-
</Link>
71+
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
72+
<div className="flex w-full items-center gap-3 rounded-lg px-3 py-1 hover:bg-[#1A1823] transition">
73+
<RequestModal/>
74+
</div>
7875
</DropdownMenuItem>
7976
<div className="flex w-full items-center gap-3 rounded-lg px-3 py-1">
8077
<div className="border rounded-full">

src/components/Navbar.tsx

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
import FloatingNavbar from "./FloatingNavbar";
1616
import PWAInstallButton from "./ui/PWAInstallButton";
1717
import SearchBarChild from "./Searchbar/searchbar-child";
18-
import Banner from "@/components/bannerDismiss";
18+
import Banner from "@/components/ui/banners/bannerDismiss";
1919
import type { ICourses } from "@/interface";
2020
import {
2121
DropdownMenu,
@@ -25,7 +25,7 @@ import {
2525
} from "@/components/ui/dropdown-menu";
2626
import { useCourses } from "@/context/courseContext";
2727
import PinnedModal from "./ui/PinnedModal";
28-
import CookoffBanner from "./CookoffBanner";
28+
import RequestModal from "./ui/RequestModal";
2929

3030
function Navbar() {
3131
const pathname: string = usePathname() ?? "/";
@@ -52,27 +52,25 @@ function Navbar() {
5252
</span>
5353
</div>
5454

55-
<Link href="/request" className="ml-2 mt-2 sm:mt-0">
56-
<div className="flex h-8 items-center gap-1 rounded-full border border-[#3A3745] bg-[#e8e9ff] px-2.5 py-1 text-xs font-semibold text-gray-700 transition hover:bg-slate-50 dark:bg-black dark:text-white dark:hover:bg-[#1A1823] sm:h-9 sm:gap-2 sm:px-3.5 sm:py-1.5 sm:text-sm md:h-10 md:px-4 md:py-2 md:text-base">
57-
<ArrowUpRight className="h-3.5 w-3.5 sm:h-4 sm:w-4" />
58-
<span className="truncate">Paper Request</span>
59-
</div>
60-
</Link>
55+
<div className="flex h-8 items-center gap-1 rounded-full border border-[#3A3745] bg-[#e8e9ff] px-2.5 py-1 text-xs font-semibold text-gray-700 transition hover:bg-slate-50 dark:bg-black dark:text-white dark:hover:bg-[#1A1823] sm:h-9 sm:gap-2 sm:px-3.5 sm:py-1.5 sm:text-sm md:h-10 md:px-4 md:py-2 md:text-base">
56+
<span className="truncate">
57+
<RequestModal/>
58+
</span>
59+
</div>
6160
</>
6261
);
6362

6463
return (
6564
<div className="sticky top-0 z-[50] w-full bg-[#B2B8FF] dark:bg-[#130E1F]">
66-
{/* <Banner
65+
<Banner
6766
bannerId="freshers"
6867
bgColor="#fef3c7"
6968
textColor="#5a3000"
7069
iconColor="#d97706"
7170
accentColor="#78350f"
7271
title="Attention Freshers!"
7372
message="If papers for your subject are not yet available, click on your subject and explore related subjects until papers become available, as these are newly introduced courses."
74-
/> */}
75-
<CookoffBanner />
73+
/>
7674
<div className="flex items-center justify-between bg-inherit px-4 py-4 md:px-8 md:py-5">
7775
{}
7876
<div className="relative flex items-center gap-4">
@@ -113,11 +111,8 @@ function Navbar() {
113111
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
114112
<PinnedModal/>
115113
</DropdownMenuItem>
116-
<DropdownMenuItem asChild>
117-
<Link href="/request" className="flex items-center gap-3">
118-
<ArrowUpRight className="h-4 w-4" />
119-
<span className="font-medium">Paper Request</span>
120-
</Link>
114+
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
115+
<RequestModal/>
121116
</DropdownMenuItem>
122117
</DropdownMenuContent>
123118
</DropdownMenu>
@@ -141,7 +136,7 @@ function Navbar() {
141136
</DropdownMenuTrigger>
142137

143138
<DropdownMenuContent
144-
className="xl:hidden mt-2 py-2 w-72 space-y-1 rounded-3xl
139+
className="xl:hidden flex flex-col gap-1 py-3 w-72 space-y-1 rounded-3xl px-4
145140
border border-[#3A3745] shadow-lg backdrop-blur-sm transition-colors
146141
bg-[#e8e9ff] text-gray-700
147142
dark:bg-black dark:text-white dark:border-[#3A3745]"
@@ -150,16 +145,13 @@ function Navbar() {
150145
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
151146
<PinnedModal/>
152147
</DropdownMenuItem>
153-
<DropdownMenuItem asChild>
154-
<Link href="/request" className="flex items-center gap-3">
155-
<ArrowUpRight className="h-4 w-4" />
156-
<span className="font-medium">Paper Request</span>
157-
</Link>
148+
<DropdownMenuItem asChild onSelect={(e) => e.preventDefault()}>
149+
<RequestModal/>
158150
</DropdownMenuItem>
159151
</DropdownMenuContent>
160152
</DropdownMenu>
161153
</div>
162-
<div className="hidden h-10 items-center xl:flex">
154+
<div className="hidden h-10 items-center xl:flex gap-2">
163155
{renderHomePageButtons()}
164156
</div>
165157
</>

src/components/ui/RequestModal.tsx

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
"use client";
2+
3+
import React from 'react'
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogDescription,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger,
11+
} from "@/components/ui/dialog"
12+
import { ArrowUpRight } from 'lucide-react'
13+
import { Input } from "@/components/ui/input";
14+
import { useState, useRef, useEffect, useMemo } from 'react';
15+
import { Search } from "lucide-react";
16+
import {
17+
Select,
18+
SelectContent,
19+
SelectItem,
20+
SelectTrigger,
21+
SelectValue,
22+
} from "@/components/ui/select";
23+
import { Button } from "@/components/ui/button";
24+
import axios from 'axios';
25+
import toast from 'react-hot-toast';
26+
import { exams, slots, years } from "@/components/select_options";
27+
import Fuse from 'fuse.js';
28+
import { useCourses } from "@/context/courseContext";
29+
30+
type Course = {
31+
name?: string | null;
32+
courseName?: string | null;
33+
title?: string | null;
34+
};
35+
36+
const RequestModal = () => {
37+
const [open, setOpen] = useState(false);
38+
const [subjects, setSubjects] = useState<string[]>([]);
39+
const [searchText, setSearchText] = useState("");
40+
const [suggestions, setSuggestions] = useState<string[]>([]);
41+
const suggestionsRef = useRef<HTMLUListElement | null>(null);
42+
const [selectedSubject, setSelectedSubject] = useState<string | null>(null);
43+
const [selectedExam, setSelectedExam] = useState<string | null>(null);
44+
const [selectedSlot, setSelectedSlot] = useState<string | null>(null);
45+
const [selectedYear, setSelectedYear] = useState<string | null>(null);
46+
const { courses, loading, error, refetch } = useCourses();
47+
48+
useEffect(() => {
49+
setSubjects(courses.map((course) => course.name));
50+
}, [courses]);
51+
52+
53+
const resetModal = () => {
54+
setSearchText("");
55+
setSelectedSubject(null);
56+
setSelectedExam(null);
57+
setSelectedSlot(null);
58+
setSelectedYear(null);
59+
setSuggestions([]);
60+
};
61+
62+
63+
const handleSelectSubject = (subject: string) => {
64+
setSelectedSubject(subject);
65+
setSearchText(subject);
66+
setSuggestions([]);
67+
setSelectedExam(null);
68+
setSelectedSlot(null);
69+
setSelectedYear(null);
70+
};
71+
72+
const handleSubmit = async () => {
73+
if (!selectedSubject || !selectedExam || !selectedSlot || !selectedYear) {
74+
toast.error("Please fill all fields before submitting.");
75+
return;
76+
}
77+
78+
try {
79+
await toast.promise(
80+
axios.post("/api/request", {
81+
subject: selectedSubject,
82+
exam: selectedExam,
83+
slot: selectedSlot,
84+
year: selectedYear,
85+
}),
86+
{
87+
loading: "Submitting your request...",
88+
success: "Your paper request was submitted successfully",
89+
error: "Failed to submit your request. Please try again later.",
90+
},
91+
);
92+
93+
setOpen(false);
94+
} catch (error) {
95+
console.error("Error submitting request:", error);
96+
}
97+
};
98+
const fuse = useMemo(
99+
() => new Fuse(subjects, { includeScore: true, threshold: 0.3 }),
100+
[subjects],
101+
);
102+
103+
useEffect(() => {
104+
if (!searchText.trim()) {
105+
setSuggestions([]);
106+
return;
107+
}
108+
109+
if (selectedSubject && searchText === selectedSubject) {
110+
setSuggestions([]);
111+
return;
112+
}
113+
const results = fuse.search(searchText);
114+
setSuggestions(results.map((r) => r.item).slice(0, 10));
115+
}, [searchText, fuse, selectedSubject]);
116+
117+
return (
118+
<Dialog open={open} onOpenChange={(isOpen) => {
119+
setOpen(isOpen);
120+
if (isOpen) resetModal();
121+
}}>
122+
<DialogTrigger className='flex items-center gap-2'>
123+
<ArrowUpRight className="h-4 w-4"/>
124+
<span className="font-medium">Request Paper</span>
125+
</DialogTrigger>
126+
<DialogContent className='bg-[#F3F5FF] dark:bg-[#070114] border-[#3A3745] items-start'>
127+
<DialogHeader>
128+
<DialogTitle>Request Papers</DialogTitle>
129+
<DialogDescription>
130+
Having trouble finding a specific paper? Don&apos;t worry! Simply submit a request here, and our team will track it down, source it, and upload it for you so you can access it hassle-free.
131+
</DialogDescription>
132+
</DialogHeader>
133+
<div className="text-end">
134+
<div className="relative mx-auto mt-4 mb-8 max-w-xl font-play">
135+
<Input
136+
type="text"
137+
value={searchText}
138+
onChange={(e) => setSearchText(e.target.value)}
139+
placeholder="Search by subject..."
140+
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" : ""}`}
141+
/>
142+
<button
143+
type="button"
144+
className="absolute inset-y-0 right-0 flex items-center pr-3"
145+
>
146+
<Search className="h-5 w-5 text-black dark:text-white" />{" "}
147+
</button>
148+
{suggestions.length > 0 && (
149+
<ul
150+
ref={suggestionsRef}
151+
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]"
152+
>
153+
{suggestions.map((s, idx) => (
154+
<li
155+
key={idx}
156+
onClick={() => handleSelectSubject(s)}
157+
className="cursor-pointer truncate p-2 hover:bg-gray-100 dark:hover:bg-gray-800"
158+
>
159+
{s}
160+
</li>
161+
))}
162+
</ul>
163+
)}
164+
</div>
165+
166+
<div className="mb-8 flex justify-center gap-4 w-full">
167+
<Select
168+
onValueChange={setSelectedExam}
169+
disabled={!selectedSubject}
170+
value={selectedExam ?? undefined}
171+
>
172+
<SelectTrigger className="flex-1 dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]">
173+
<SelectValue placeholder="Exam" />
174+
</SelectTrigger>
175+
<SelectContent className='dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]'>
176+
{exams.map((exam) => (
177+
<SelectItem key={exam} value={exam} className='cursor-pointer hover:bg-slate-50 dark:hover:bg-[#1A1823]'>
178+
{exam}
179+
</SelectItem>
180+
))}
181+
</SelectContent>
182+
</Select>
183+
<Select
184+
onValueChange={setSelectedSlot}
185+
disabled={!selectedSubject}
186+
value={selectedSlot ?? undefined}
187+
>
188+
<SelectTrigger className="flex-1 dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]">
189+
<SelectValue placeholder="Slot" />
190+
</SelectTrigger>
191+
<SelectContent className='dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]'>
192+
{slots.map((slot) => (
193+
<SelectItem key={slot} value={slot} className='cursor-pointer hover:bg-slate-50 dark:hover:bg-[#1A1823]'>
194+
{slot}
195+
</SelectItem>
196+
))}
197+
</SelectContent>
198+
</Select>
199+
<Select
200+
onValueChange={setSelectedYear}
201+
disabled={!selectedSubject}
202+
value={selectedYear ?? undefined}
203+
>
204+
<SelectTrigger className="flex-1 dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]">
205+
<SelectValue placeholder="Year" />
206+
</SelectTrigger>
207+
<SelectContent className='dark:bg-black dark:text-white border-[#3A3745] bg-[#e8e9ff]'>
208+
{[...years]
209+
.sort((a, b) => Number(b) - Number(a))
210+
.map((year) => (
211+
<SelectItem key={year} value={year} className='cursor-pointer hover:bg-slate-50 dark:hover:bg-[#1A1823]'>
212+
{year}
213+
</SelectItem>
214+
))}
215+
</SelectContent>
216+
</Select>
217+
</div>
218+
219+
<Button
220+
className="rounded-md px-8 py-3 hover:opacity-80 bg-[#B2B8FF] text-black dark:border-[#36266D] dark:bg-[#7480ff9d] dark:text-white"
221+
onClick={handleSubmit}
222+
>
223+
Submit
224+
</Button>
225+
</div>
226+
</DialogContent>
227+
</Dialog>
228+
)
229+
}
230+
231+
export default RequestModal
File renamed without changes.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { Button } from "./ui/button";
3+
import { Button } from "../button";
44
import Image from "next/image";
55
import Link from "next/link";
66

File renamed without changes.

0 commit comments

Comments
 (0)