Skip to content

Commit

Permalink
feat: new library
Browse files Browse the repository at this point in the history
  • Loading branch information
Rahuletto committed Feb 4, 2025
1 parent a3cebe2 commit 448624a
Show file tree
Hide file tree
Showing 17 changed files with 660 additions and 211 deletions.
2 changes: 1 addition & 1 deletion app/academia/library/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CoursePapers } from "../library";
import type { CoursePapers } from "../pyq/PYQ";
import CourseCode from "./Code";
import PaperLink from "./PaperLink";

Expand Down
53 changes: 53 additions & 0 deletions app/academia/library/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"use client";
import React, { useEffect, useRef } from "react";
import { LuSquareSlash } from "react-icons/lu";

interface SearchBarProps {
searchQuery: string;
setSearchQuery: (query: string) => void;
}

export function SearchBar({ searchQuery, setSearchQuery }: SearchBarProps) {
const searchbox = useRef<HTMLInputElement>(null);
const [isMounted, setIsMounted] = React.useState(false);

useEffect(() => {
function keyHandler(e: KeyboardEvent) {
if (e.metaKey && e.key === "k") {
e.preventDefault();
searchbox.current?.focus();
} else if (e.key === "/") {
e.preventDefault();
searchbox.current?.focus();
}
if (e.key === "Escape" && searchbox.current) {
searchbox.current.blur();
}
}

setIsMounted(true);
window.addEventListener("keydown", keyHandler);

return () => {
window.removeEventListener("keydown", keyHandler);
};
}, []);

if (!isMounted) return null;
return (
<div className="sticky z-50 bottom-6 left-0 flex w-full items-center justify-center duration-200 focus-within:mb-8 focus-within:scale-105 active:mb-8 active:scale-105">
<input
tabIndex={0}
ref={searchbox}
type="text"
placeholder="Search files and folders"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="relative z-10 w-[250px] animate-fastfade rounded-xl bg-light-button px-4 py-2 text-lg font-medium shadow-lg outline-hidden backdrop-blur-lg dark:backdrop-blur-lg transition-all duration-200 md:w-[350px] dark:bg-dark-button"
/>
<div className="relative right-8 z-10 text-xl opacity-50">
<LuSquareSlash />
</div>
</div>
);
}
256 changes: 66 additions & 190 deletions app/academia/library/library.tsx
Original file line number Diff line number Diff line change
@@ -1,194 +1,70 @@
"use client";
import React, { useEffect, useState } from "react";
import Fuse from "fuse.js";
import type { Course } from "@/types/Course";
import Card from "./components/Card";
import { Link } from 'next-view-transitions'
import Image from 'next/image'
import React from 'react'

export type CoursePapers = {
name: string;
code: string;
urls: {
semester: string;
urls: {
period: string;
url: string;
raw: string;
}[];
}[];
};
export default function library() {
return (
<main
className="relative p-2 flex items-center justify-center flex-col h-full w-full gap-5"
style={{
fontFamily: "'Bricolage Grotesque'",
}}
>
<div className="flex flex-col justify-center mb-6 px-2 gap-5 items-center">
<div
id="title"
className="title transition duration-200 flex flex-col gap-1 dark:text-dark-color items-center justify-start text-light-color"
>
<h1 className="font-semibold text-4xl">Library.</h1>

export default function Library({ courses, files }: { courses: Course[]; files: CoursePapers[] }) {
const [searchQuery, setSearchQuery] = useState("");
const [semester, setSemester] = useState(0);
const [searchResults, setSearchResults] = useState<CoursePapers[]>([]);
const sortedFiles = files.sort((a, b) =>
a.code.startsWith("21") ? -1 : b.code.startsWith("21") ? 1 : 0,
);
const [mounted, setMounted] = useState(false);
<p className="dark:text-dark-color text-light-color text-center transition duration-200 opacity-50 text-md font-medium">
Get access to Semester previous year papers or resources, all from ClassPro.
</p>
</div>
</div>
<div className='flex gap-4 flex-col items-center justify-center md:flex-row'>
<Link href="/academia/library/pyq">
<div className="relative">
<div className="relative">
<Image
src="/library/sem.png"
className='rounded-3xl border-2 aspect-square object-cover object-left-top border-light-accent dark:border-dark-accent'

useEffect(() => {
setMounted(true);
}, []);

const fuse = new Fuse(sortedFiles, {
keys: ["name", "code"],
threshold: 0.3,
});

const handleSearch = (query: string) => {
setSearchQuery(query);
if (query.length > 1) {
const results = fuse.search(query).map((result) => result.item);
setSearchResults(results);
} else {
setSearchResults([]);
}
};

return (
<main
className="relative p-2"
style={{
fontFamily: "'Bricolage Grotesque'",
}}
>
<div className="flex flex-col md:flex-row justify-between mb-6 px-2 gap-5 items-center">
<div
id="title"
className="title transition duration-200 flex flex-col gap-1 dark:text-dark-color items-center md:items-start justify-start text-light-color"
>
<h1 className="font-semibold text-4xl">Library.</h1>

<p className="dark:text-dark-color text-light-color transition duration-200 opacity-50 text-md font-medium">
Past exam papers at your fingertips.
</p>
</div>
<input
type="text"
placeholder="Search for papers"
value={searchQuery}
onChange={(e) => handleSearch(e.target.value)}
className="w-full py-2 px-4 rounded-xl focus:outline-hidden bg-light-input dark:bg-dark-input max-w-80 text-light-color dark:text-dark-color"
/>
</div>

<div className="bg-light-background-light/50 pb-16 relative min-h-screen shadow-lg dark:bg-dark-background-darker/50 backdrop-blur-xs w-full h-full rounded-t-3xl p-4">
<div className="flex overflow-x-auto pb-4 gap-4 items-center mb-2 justify-start w-full">
<button
type="button"
onClick={() => setSemester(0)}
disabled={searchQuery.length >= 1}
className={`${
searchQuery.length < 1 && semester === 0
? "dark:bg-dark-accent bg-light-accent rounded-full text-light-background-light dark:text-dark-background-darker"
: "dark:bg-dark-accent/20 bg-light-accent/20 rounded-xl text-light-accent dark:text-dark-accent"
} transition-all px-3 py-2 min-w-32 text-base w-full disabled:opacity-20 disabled:cursor-not-allowed font-semibold`}
>
My Papers
</button>
<div className="h-9 w-3 rounded-full bg-light-background-dark dark:bg-dark-background-light" />
{Array.from({ length: 8 }, (_, i) => (
<button
key={i}
type="button"
onClick={() => setSemester(i + 1)}
disabled={searchQuery.length >= 1}
className={`${
searchQuery.length < 1 && semester === i + 1
? "dark:bg-dark-accent bg-light-accent rounded-full text-light-background-light dark:text-dark-background-darker"
: "dark:bg-dark-accent/20 bg-light-accent/20 rounded-xl text-light-accent dark:text-dark-accent"
} transition-all duration-500 px-3 py-2 min-w-32 text-base w-full disabled:opacity-20 disabled:cursor-not-allowed font-semibold`}
>
Semester {i + 1}
</button>
))}
</div>
{semester === 0 ? null : (
<p className="w-full text-center text-light-color dark:text-dark-color gap-1 flex items-center justify-center opacity-40 font-medium text-sm mb-4">
Results:{" "}
<strong className="text-light-accent dark:text-dark-accent">
{searchResults.length > 1
? searchResults.length
: sortedFiles.filter((file) =>
file.urls.some((url) => Number(url.semester) === semester),
).length}{" "}
courses
</strong>{" "}
with{" "}
<strong className="text-light-accent dark:text-dark-accent">
{searchResults.length > 1
? searchResults.reduce(
(acc, file) =>
acc +
file.urls.reduce(
(urlAcc, urlGroup) => urlAcc + urlGroup.urls.length,
0,
),
0,
)
: sortedFiles
.filter((file) =>
file.urls.some(
(url) => Number(url.semester) === semester,
),
)
.reduce(
(acc, file) =>
acc +
file.urls.reduce(
(urlAcc, urlGroup) => urlAcc + urlGroup.urls.length,
0,
),
0,
)}{" "}
papers
</strong>
</p>
)}
{mounted && (
<div className="grid xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4">
{(searchResults.length >= 1
? searchResults.sort((a, b) => {
const aSemester = Math.min(
...a.urls.map((url) => Number(url.semester)),
);
const bSemester = Math.min(
...b.urls.map((url) => Number(url.semester)),
);
return aSemester - bSemester;
})
: semester === 0
? sortedFiles.filter((a) =>
courses.some((b) => a.code === b.code),
)
: sortedFiles.filter((file) =>
file.urls.some((url) => Number(url.semester) === semester),
)
).map((result, index) => (
<Card result={result} key={index} />
))}
</div>
)}
<p className="w-full absolute bottom-6 text-center text-light-color dark:text-dark-color gap-1 flex items-center justify-center opacity-40 font-medium text-sm mt-4">
Database of
<strong className="text-light-accent dark:text-dark-accent">
{sortedFiles.length} courses
</strong>{" "}
with{" "}
<strong className="text-light-accent dark:text-dark-accent">
{sortedFiles.reduce(
(acc, file) =>
acc +
file.urls.reduce(
(urlAcc, urlGroup) => urlAcc + urlGroup.urls.length,
0,
),
0,
)}{" "}
papers
</strong>
</p>
</div>
</main>
);
width={400}
height={500}
alt="Sem papers"
/>
<div className="absolute inset-x-0 bottom-0 h-72 bg-gradient-to-t from-black to-transparent rounded-b-3xl" />
</div>
<div className='absolute bottom-0 left-0 flex flex-col gap-1 p-8'>
<h1 className="font-semibold text-2xl">Sem PYQs.</h1>
<p className="dark:text-dark-color text-light-color transition duration-200 opacity-50 text-base font-medium">
Get your hands on all the semester papers you need.
</p>
</div>
</div>
</Link>
<Link href="/academia/library/resources">
<div className="relative">
<div className="relative">
<Image
src="/library/resources.png"
className='rounded-3xl aspect-square border-2 object-cover object-left-top border-light-accent dark:border-dark-accent'
width={400}
height={500}
alt="Resource papers"
/>
<div className="absolute inset-x-0 bottom-0 h-72 bg-gradient-to-t from-black to-transparent rounded-b-3xl" />
</div>
<div className='absolute bottom-0 left-0 flex flex-col gap-1 p-8'>
<h1 className="font-semibold text-2xl">Resources.</h1>
<p className="dark:text-dark-color text-light-color transition duration-200 opacity-50 text-base font-medium">
A dumpster fire of available files from the web.
</p>
</div>
</div>
</Link>
</div>
</main>
)
}
6 changes: 3 additions & 3 deletions app/academia/library/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import Library from "./library";
import Library from "./Library";
import { fetchUserData } from "@/hooks/fetchUserData";
import { fetchFileArray } from "@/hooks/fetchFiles";
import PayRequired from "../../payment";
Expand Down Expand Up @@ -28,6 +28,6 @@ export default async function Docupro() {
) {
return <PayRequired />;
}
const files = await fetchFileArray();
return <Library courses={courses.courses} files={files} />;

return <Library />;
}
Loading

0 comments on commit 448624a

Please sign in to comment.