Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/api/user-papers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ export async function POST(req: Request) {
{ status: 500 },
);
}
}
}
249 changes: 134 additions & 115 deletions src/components/PinnedPapersCarousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ function PinnedPapersCarousel() {
const [displayPapers, setDisplayPapers] = useState<IUpcomingPaper[]>([]);
useEffect(() => {
const handleResize = () => {
if(window.innerWidth <= 540){
if (window.innerWidth <= 540) {
setChunkSize(2);
}
else if (window.innerWidth <= 920) {
} else if (window.innerWidth <= 920) {
setChunkSize(4);
} else {
setChunkSize(8);
Expand All @@ -48,7 +47,9 @@ function PinnedPapersCarousel() {
{ subject: "add_subject_button", slots: [] } as IUpcomingPaper,
];
} else {
chunkedPapers.push([{ subject: "add_subject_button", slots: [] } as IUpcomingPaper]);
chunkedPapers.push([
{ subject: "add_subject_button", slots: [] } as IUpcomingPaper,
]);
}
}

Expand Down Expand Up @@ -96,42 +97,41 @@ function PinnedPapersCarousel() {
}, []);

useEffect(() => {
const handleSubjectsChange = () => {
void (async () => {
try {
const storedSubjects = JSON.parse(
localStorage.getItem("userSubjects") ?? "[]",
) as StoredSubjects;

const response = await axios.post<{ subject: string; slots: string[] }[]>(
"/api/user-papers",
storedSubjects,
);

const fetchedPapers = response.data;

const fetchedSubjectsSet = new Set(
fetchedPapers.map((paper) => paper.subject),
);

const storedSubjectsArray = Array.isArray(storedSubjects)
? storedSubjects
: [];
const missingSubjects = storedSubjectsArray
.filter((subject: string) => !fetchedSubjectsSet.has(subject))
.map((subject: string) => ({
subject,
slots: [],
})) as { subject: string; slots: string[] }[];

const allDisplayPapers = [...fetchedPapers, ...missingSubjects];

setDisplayPapers(allDisplayPapers);
} catch (error) {
console.error("Failed to fetch papers:", error);
}
})();
};
const handleSubjectsChange = () => {
void (async () => {
try {
const storedSubjects = JSON.parse(
localStorage.getItem("userSubjects") ?? "[]",
) as StoredSubjects;

const response = await axios.post<
{ subject: string; slots: string[] }[]
>("/api/user-papers", storedSubjects);

const fetchedPapers = response.data;

const fetchedSubjectsSet = new Set(
fetchedPapers.map((paper) => paper.subject),
);

const storedSubjectsArray = Array.isArray(storedSubjects)
? storedSubjects
: [];
const missingSubjects = storedSubjectsArray
.filter((subject: string) => !fetchedSubjectsSet.has(subject))
.map((subject: string) => ({
subject,
slots: [],
})) as { subject: string; slots: string[] }[];

const allDisplayPapers = [...fetchedPapers, ...missingSubjects];

setDisplayPapers(allDisplayPapers);
} catch (error) {
console.error("Failed to fetch papers:", error);
}
})();
};

window.addEventListener("userSubjectsChanged", handleSubjectsChange);

Expand All @@ -143,83 +143,102 @@ function PinnedPapersCarousel() {
const plugins = [Autoplay({ delay: 8000, stopOnInteraction: true })];

return (
<div className="px-4 mt-8 md:mt-4">
<div className="mt-8 px-4 md:mt-4">
<div className="">
{displayPapers.length > 0 ?
<Carousel
opts={{
align: "start",
loop: true,
}}
plugins={plugins}
className="w-full"
>
{(() => {
const totalItems = displayPapers.length + 1;
const needsNav = totalItems > chunkSize;
return needsNav ? (
<div className="relative mt-4 flex justify-end gap-4">
<CarouselPrevious className="relative" />
<CarouselNext className="relative" />
</div>
) : null;
})()}
<CarouselContent>
{isLoading ? (
<CarouselItem
className={`grid ${
chunkSize === 2 ? "grid-cols-1 grid-rows-2" : chunkSize === 4 ? "grid-cols-2 grid-rows-2" : "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
<SkeletonPaperCard length={chunkSize} />
</CarouselItem>
) : (
chunkedPapers.map((paperGroup, index) => {
const placeholdersNeeded = (chunkSize - paperGroup.length) % chunkSize;
return (
<CarouselItem
key={`carousel-item-${index}`}
className={`grid ${
chunkSize === 2 ? "grid-cols-1 grid-rows-2" : chunkSize === 4 ? "grid-cols-2 grid-rows-2" : "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
{paperGroup.map((paper, subIndex) => (
paper.subject === "add_subject_button" ?
<div key={subIndex} className="h-full border-dashed border border-[#734DFF] dark:border-[#36266D] rounded-sm font-bold hover:bg-[#EFEAFF] dark:bg-transparent dark:hover:bg-[#1A1823] bg-[#FFFFFF]">
<PinnedModal triggerName={"Add Subjects"} page={"Carousel"}/>
</div>
:
<div key={subIndex} className="h-full">
<UpcomingPaper
subject={paper.subject}
slots={paper.slots}
/>
</div>
))}

{Array.from({ length: placeholdersNeeded }).map(
(_, placeholderIndex) => (
<div
key={`placeholder-${placeholderIndex}`}
className="invisible h-full"
></div>
),
)}
</CarouselItem>
);
})
)}
</CarouselContent>
</Carousel> :
<div className={`relative flex flex-col justify-center gap-4 items-center text-center font-bold`}
>
Start pinning subjects for quick and easy access.
<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">
<span className="truncate">
<PinnedModal/>
</span>
{displayPapers.length > 0 ? (
<Carousel
opts={{
align: "start",
loop: true,
}}
plugins={plugins}
className="w-full"
>
{(() => {
const totalItems = displayPapers.length + 1;
const needsNav = totalItems > chunkSize;
return needsNav ? (
<div className="relative mt-4 flex justify-end gap-4">
<CarouselPrevious className="relative" />
<CarouselNext className="relative" />
</div>
) : null;
})()}
<CarouselContent>
{isLoading ? (
<CarouselItem
className={`grid ${
chunkSize === 2
? "grid-cols-1 grid-rows-2"
: chunkSize === 4
? "grid-cols-2 grid-rows-2"
: "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
<SkeletonPaperCard length={chunkSize} />
</CarouselItem>
) : (
chunkedPapers.map((paperGroup, index) => {
const placeholdersNeeded =
(chunkSize - paperGroup.length) % chunkSize;
return (
<CarouselItem
key={`carousel-item-${index}`}
className={`grid ${
chunkSize === 2
? "grid-cols-1 grid-rows-2"
: chunkSize === 4
? "grid-cols-2 grid-rows-2"
: "grid-cols-4"
} gap-4 lg:auto-rows-fr`}
>
{paperGroup.map((paper, subIndex) =>
paper.subject === "add_subject_button" ? (
<div
key={subIndex}
className="h-full rounded-sm border border-dashed border-[#734DFF] bg-[#FFFFFF] font-bold hover:bg-[#EFEAFF] dark:border-[#36266D] dark:bg-transparent dark:hover:bg-[#1A1823]"
>
<PinnedModal
triggerName={"Add Subjects"}
page={"Carousel"}
/>
</div>
) : (
<div key={subIndex} className="h-full">
<UpcomingPaper
subject={paper.subject}
slots={paper.slots}
/>
</div>
),
)}

{Array.from({ length: placeholdersNeeded }).map(
(_, placeholderIndex) => (
<div
key={`placeholder-${placeholderIndex}`}
className="invisible h-full"
></div>
),
)}
</CarouselItem>
);
})
)}
</CarouselContent>
</Carousel>
) : (
<div
className={`relative flex flex-col items-center justify-center gap-4 text-center font-bold`}
>
Start pinning subjects for quick and easy access.
<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">
<span className="truncate">
<PinnedModal />
</span>
</div>
</div>
</div>}
)}
</div>
</div>
);
Expand Down
Loading