Skip to content

Commit 1f16bc2

Browse files
authored
Merge pull request #494 from CodeChefVIT/staging
feat: pdf engine, preview and UX fix
2 parents cf7bc1f + 9833bcc commit 1f16bc2

5 files changed

Lines changed: 229 additions & 123 deletions

File tree

src/components/Card.tsx

Lines changed: 113 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
"use client";
22

3+
import React from "react";
34
import { type IPaper } from "@/interface";
45
import Image from "next/image";
6+
import { X } from "lucide-react";
57
import { Eye, Download, Check } from "lucide-react";
68
import {
79
extractBracketContent,
@@ -13,16 +15,18 @@ import {
1315
downloadFile,
1416
} from "@/lib/utils/download";
1517
import { Capsule } from "@/components/ui/capsule";
16-
import Link from "next/link";
1718
import { cn } from "@/lib/utils";
1819

1920
interface CardProps {
2021
paper: IPaper;
2122
onSelect: (paper: IPaper, isSelected: boolean) => void;
2223
isSelected: boolean;
24+
isShow?: boolean;
2325
}
2426

25-
const Card = ({ paper, onSelect, isSelected }: CardProps) => {
27+
const Card = ({ paper, onSelect, isSelected, isShow=true }: CardProps) => {
28+
const [previewOpen, setPreviewOpen] = React.useState(false);
29+
const [iframeLoading, setIframeLoading] = React.useState(true);
2630
const handleDownload = async (paper: IPaper) => {
2731
await downloadFile(getSecureUrl(paper.file_url), generateFileName(paper));
2832
};
@@ -34,76 +38,124 @@ const Card = ({ paper, onSelect, isSelected }: CardProps) => {
3438
const paperLink = `/paper/${paper._id}`;
3539

3640
return (
37-
<div
38-
className={cn(
39-
"overflow-hidden rounded-sm border-2 border-[#734DFF] bg-[#FFFFFF] font-play transition-all duration-150 hover:bg-[#EFEAFF] dark:border-[#36266D] dark:bg-[#171720] hover:dark:bg-[#262635]",
40-
isSelected && "bg-white",
41-
)}
42-
>
43-
<Link href={paperLink} target="_blank" rel="noopener noreferrer">
44-
<Image
45-
src={paper.thumbnail_url}
46-
alt={paper.subject}
47-
width={320}
48-
height={180}
49-
className="w-full object-cover p-4 pb-3 md:h-[250px]"
50-
/>
41+
<>
42+
<div
43+
onClick={(e) => {
44+
const target = e.target as HTMLElement;
5145

52-
<div className="justify-center">
53-
<div className="flex flex-row items-center justify-between px-4 pb-2">
54-
<div className="text-md font-play font-medium">
55-
{extractBracketContent(paper.subject)}
56-
</div>
57-
<div className="flex gap-2">
58-
<Link href={paperLink} target="_blank" rel="noopener noreferrer">
59-
<Eye size={22} />
60-
</Link>
61-
<Download
62-
size={20}
63-
onClick={(e) => {
64-
e.preventDefault();
65-
e.stopPropagation();
66-
void handleDownload(paper);
67-
}}
68-
className="cursor-pointer"
69-
/>
46+
if (target.closest("button, input, svg")) return;
47+
48+
window.open(paperLink, "_blank");
49+
}}
50+
className={cn(
51+
"cursor-pointer overflow-hidden rounded-sm border-2 border-[#734DFF] bg-[#FFFFFF] font-play transition-all duration-150 hover:bg-[#EFEAFF] dark:border-[#36266D] dark:bg-[#171720] hover:dark:bg-[#262635]",
52+
isSelected && "ring-2 ring-[#7480FF] bg-[#EFEAFF]"
53+
)}
54+
>
55+
<Image
56+
src={paper.thumbnail_url}
57+
alt={paper.subject}
58+
width={320}
59+
height={180}
60+
className="w-full object-cover p-4 pb-3 md:h-[250px]"
61+
/>
62+
<div className="justify-center">
63+
<div className="flex flex-row items-center justify-between px-4 pb-2">
64+
<div className="text-md font-play font-medium">
65+
{extractBracketContent(paper.subject)}
66+
</div>
7067
</div>
71-
</div>
7268

73-
<div className="h-[1px] w-full bg-[#734DFF] dark:bg-[#36266D]" />
69+
<div className="h-[1px] w-full bg-[#734DFF] dark:bg-[#36266D]" />
7470

75-
<div className="space-y-2 p-4">
76-
<div className="font-play text-lg font-semibold">
77-
{extractWithoutBracketContent(paper.subject)}
78-
</div>
79-
<div className="flex flex-wrap gap-2">
80-
<Capsule>{paper.exam}</Capsule>
81-
<Capsule>{paper.slot}</Capsule>
82-
<Capsule>{paper.year}</Capsule>
83-
<Capsule>{paper.semester}</Capsule>
71+
<div className="space-y-2 p-4">
72+
<div className="font-play text-lg font-semibold">
73+
{extractWithoutBracketContent(paper.subject)}
74+
</div>
75+
<div className="flex flex-wrap gap-2">
76+
<Capsule>{paper.exam}</Capsule>
77+
<Capsule>{paper.slot}</Capsule>
78+
<Capsule>{paper.year}</Capsule>
79+
<Capsule>{paper.semester}</Capsule>
80+
</div>
8481
</div>
8582
</div>
86-
</div>
87-
</Link>
8883

89-
<div className="flex items-center justify-between gap-2 px-4 pb-4 font-play">
90-
<div className="flex items-center gap-2">
91-
<input
92-
checked={isSelected}
93-
onChange={handleCheckboxChange}
94-
className="h-5 w-5 accent-[#7480FF]"
95-
type="checkbox"
84+
<div className="flex justify-end gap-2 px-4 pb-2">
85+
<Eye
86+
className="cursor-pointer transition-all duration-200 ease-out hover:scale-110"
87+
onClick={(e) => {
88+
e.stopPropagation();
89+
setIframeLoading(true);
90+
setPreviewOpen(true);
91+
}}
92+
/>
93+
94+
<Download
95+
size={20}
96+
onClick={(e) => {
97+
e.stopPropagation();
98+
void handleDownload(paper);
99+
}}
100+
className="cursor-pointer"
96101
/>
97-
<p>Select</p>
98102
</div>
99-
{paper.answer_key_included && (
100-
<div className="flex items-center gap-2 font-normal text-[#7480FF]">
101-
<Check color="#7480FF" />
102-
Answer Key
103-
</div>
104-
)}
103+
104+
<div className="flex items-center justify-between gap-2 px-4 pb-4 font-play">
105+
{isShow && <div className="flex items-center gap-2">
106+
<input
107+
checked={isSelected}
108+
onChange={(e) => {
109+
e.stopPropagation();
110+
handleCheckboxChange();
111+
}}
112+
onClick={(e) => e.stopPropagation()}
113+
className="h-5 w-5 accent-[#7480FF]"
114+
type="checkbox"
115+
/>
116+
<p>Select</p>
117+
</div>}
118+
119+
{paper.answer_key_included && (
120+
<div className="flex items-center gap-2 font-normal text-[#7480FF]">
121+
<Check color="#7480FF" />
122+
Answer Key
123+
</div>
124+
)}
125+
</div>
105126
</div>
127+
128+
{previewOpen && (
129+
<div
130+
className="fixed inset-0 z-50 flex items-center justify-center bg-black/80"
131+
onClick={() => setPreviewOpen(false)}
132+
>
133+
<div
134+
className="relative w-[95%] max-w-5xl h-[90vh] rounded-lg bg-white p-2 dark:bg-[#171720]"
135+
onClick={(e) => e.stopPropagation()}
136+
>
137+
<div
138+
className={`absolute inset-0 z-50 flex items-center justify-center bg-[#070114] transition-opacity duration-300 ${
139+
iframeLoading ? "opacity-100" : "opacity-0 pointer-events-none"
140+
}`}
141+
>
142+
<div className="w-7 h-7 rounded-full border-2 border-white/20 border-t-white animate-spin" />
106143
</div>
144+
<button
145+
className="absolute top-3 left-6 z-50 p-2 rounded-full bg-black/60 text-white hover:bg-black transition"
146+
onClick={() => setPreviewOpen(false)}
147+
>
148+
<X size={18} />
149+
</button>
150+
<iframe
151+
src={`${getSecureUrl(paper.file_url)}#toolbar=0`}
152+
className="w-full h-full rounded-md"
153+
onLoad={() => setIframeLoading(false)}
154+
/>
155+
</div>
156+
</div>
157+
)}
158+
</>
107159
);
108160
};
109161

src/components/RelatedPaper.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ const RelatedPapers = () => {
9090
{relatedPapers.length === 0 ? (
9191
<p className="font-play">No related papers found.</p>
9292
) : (
93-
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
93+
<div className=" cursor-pointer grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
9494
{relatedPapers.map((paper) => (
9595
<Card
9696
key={paper._id}
@@ -99,6 +99,7 @@ const RelatedPapers = () => {
9999
("");
100100
}}
101101
isSelected={false}
102+
isShow={false}
102103
/>
103104
))}
104105
</div>

src/components/SideBar.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ function SideBar() {
1717
selectedAnswerKeyIncluded,
1818
filterOptions,
1919
handleApplyFilters,
20-
setCurrentPage,
2120
} = useFilters();
2221
const exams =
2322
filterOptions?.unique_exams.map((exam) => ({ label: exam, value: exam })) ??

src/components/SidebarSection.tsx

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,35 +21,45 @@ const SidebarSection: React.FC<SidebarSectionProps> = ({
2121
data,
2222
selected,
2323
updater,
24-
}) => (
25-
<div className="flex w-full flex-col items-baseline justify-between border-b-2 border-[#36266d] px-[10px]">
26-
<Accordion className="w-full" type="single" collapsible>
27-
<AccordionItem className="border-none no-underline" value={label}>
28-
<AccordionTrigger className="w-full no-underline">
29-
<div className="font-play text-sm no-underline">{label}</div>
30-
</AccordionTrigger>
31-
<AccordionContent>
32-
<div className="my-2 flex w-full flex-wrap items-center">
33-
{data.map((item) => (
34-
<SidebarButton
35-
key={item.value}
36-
selected={selected.includes(item.value)}
37-
onClick={() => {
38-
const newValues = selected.includes(item.value)
39-
? selected.filter((v) => v !== item.value)
40-
: [...selected, item.value];
41-
updater(newValues);
42-
}}
43-
className="mb-2 mr-2"
44-
>
45-
{item.label}
46-
</SidebarButton>
47-
))}
48-
</div>
49-
</AccordionContent>
50-
</AccordionItem>
51-
</Accordion>
52-
</div>
53-
);
24+
}) => {
25+
return (
26+
<div className="flex w-full flex-col items-baseline justify-between border-b-2 border-[#36266d] px-[10px]">
27+
<Accordion
28+
className="w-full"
29+
type="single"
30+
collapsible
31+
>
32+
<AccordionItem
33+
className="border-none"
34+
value={label} // ✅ IMPORTANT: stable value
35+
>
36+
<AccordionTrigger className="w-full">
37+
<div className="font-play text-sm">{label}</div>
38+
</AccordionTrigger>
5439

55-
export default SidebarSection;
40+
<AccordionContent>
41+
<div className="my-2 flex w-full flex-wrap items-center">
42+
{data.map((item) => (
43+
<SidebarButton
44+
key={item.value}
45+
selected={selected.includes(item.value)}
46+
onClick={() => {
47+
const newValues = selected.includes(item.value)
48+
? selected.filter((v) => v !== item.value)
49+
: [...selected, item.value];
50+
updater(newValues);
51+
}}
52+
className="mb-2 mr-2"
53+
>
54+
{item.label}
55+
</SidebarButton>
56+
))}
57+
</div>
58+
</AccordionContent>
59+
</AccordionItem>
60+
</Accordion>
61+
</div>
62+
);
63+
};
64+
65+
export default SidebarSection;

0 commit comments

Comments
 (0)