Skip to content

Commit

Permalink
Merge pull request #2 from bookpanda/link-responsive
Browse files Browse the repository at this point in the history
Link extension + responsive
  • Loading branch information
bookpanda authored Oct 9, 2024
2 parents 2315f99 + 62aa8f8 commit 0e3d4c5
Show file tree
Hide file tree
Showing 22 changed files with 556 additions and 115 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ext-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ jobs:
- name: Upload extension artifacts
uses: actions/upload-artifact@v3
with:
name: vite-web-extension
name: ourcourseville-chrome-extension
path: extension/dist
6 changes: 3 additions & 3 deletions frontend/.env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
NEXT_PUBLIC_COURSE_TTL=3600000 # 1 hour
NEXT_PUBLIC_FACULTY_TTL=86400000 # 1 day
NEXT_PUBLIC_RECENT_COURSES_TTL=31536000000 # 1 year
API_URL=http://localhost:5203
API_KEY=apikey
API_KEY=apikey
EXTENSION_URL=
Binary file modified frontend/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-toast": "^1.2.2",
"@reduxjs/toolkit": "^2.2.7",
"axios": "^1.7.7",
Expand Down
34 changes: 34 additions & 0 deletions frontend/src/app/HowTo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { FaListCheck } from "react-icons/fa6";

export const HowTo = () => {
return (
<div className="flex w-full flex-col gap-2 rounded-lg bg-white p-3 max-lg:h-auto lg:min-h-[356px] lg:py-4">
<div className="flex justify-between gap-4">
<div className="flex w-full items-center gap-2">
<FaListCheck className="h-5 w-5 text-secondary-default max-lg:h-4 max-lg:w-4" />
<div className="text-lg font-semibold text-high max-lg:text-base">
How to use extension
</div>
</div>
</div>
<div
data-orientation="horizontal"
role="separator"
className="border-disable h-px border-b"
/>
<div className="flex flex-col gap-2 overflow-x-auto overflow-y-hidden pt-2 max-md:flex">
<li>Download and unzip the extension from this web</li>
<li>
Go to{" "}
<span className="text-primary-default">chrome://extensions/</span>{" "}
(you can copy and paste this link in your browser)
</li>
<li>
Click <span className="text-primary-default">Load unpacked</span> and
select the unzipped folder
</li>
<li>Now you can share your ideas with your friends</li>
</div>
</div>
);
};
37 changes: 37 additions & 0 deletions frontend/src/app/Intro.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { FaQuestion } from "react-icons/fa";

export const Intro = () => {
return (
<div className="flex w-full flex-col gap-2 rounded-lg bg-white p-3 max-lg:h-auto lg:min-h-[356px] lg:py-4">
<div className="flex justify-between gap-4">
<div className="flex w-full items-center gap-2">
<FaQuestion className="h-5 w-5 text-secondary-default max-lg:h-4 max-lg:w-4" />
<div className="text-lg font-semibold text-high max-lg:text-base">
What is OurCourseVille
</div>
</div>
</div>
<div
data-orientation="horizontal"
role="separator"
className="border-disable h-px border-b"
/>
<div className="flex flex-col gap-2 overflow-x-auto overflow-y-hidden pt-2 max-md:flex">
<li>Tired of taking screenshots of assignments?</li>
<li>
You can now &quot;Share solution&quot; of your assignment using our
Chrome extension
</li>
<li>Share once, learn anywhere</li>
<li>
We do NOT condone any form of cheating or plagiarism, we only provide
a platform for students to collaborate and share their ideas.
</li>
<li>
We do NOT collect any Personally Identifiable Information data from
you, we only collect solutions to the assignments.
</li>
</div>
</div>
);
};
54 changes: 54 additions & 0 deletions frontend/src/app/RecentCourses.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"use client";

import { FC } from "react";
import { FaChalkboard } from "react-icons/fa6";
import { CourseCard } from "../components/Card/CourseCard";
import { useGetRecentCourses } from "../hooks/useGetRecentCourses";
import { Faculty } from "../types";

interface RecentCoursesProps {
faculties: Faculty[];
}

export const RecentCourses: FC<RecentCoursesProps> = ({ faculties }) => {
const { recentCourses } = useGetRecentCourses();

if (recentCourses.length === 0) {
return null;
}

return (
<div className="flex flex-col gap-4">
<div className="flex flex-row gap-4 max-lg:flex-col-reverse">
<div className="flex w-full flex-col gap-2 rounded-lg bg-white p-3 max-lg:w-full">
<div className="flex justify-between gap-4">
<div className="flex w-full items-center gap-2">
<FaChalkboard className="h-5 w-5 text-secondary-default max-lg:h-4 max-lg:w-4" />
<div className="text-lg font-semibold text-high max-lg:text-base">
Recent
</div>
</div>
</div>
<div
data-orientation="horizontal"
role="separator"
className="border-disable h-px border-b"
/>
<div className="grid grid-cols-3 gap-2 overflow-x-auto pt-2 max-md:flex">
{recentCourses.map((c) => {
const faculty = faculties.find((f) => f.code === c.facultyCode);
return (
<CourseCard
href={`/faculty/${c.facultyCode}/course/${c.code}/assignment`}
key={c.code}
course={c}
faculty={faculty}
/>
);
})}
</div>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { getRecordByID } from "@/src/api/record";
import { AttachmentFile } from "@/src/components/AttachmentFile";
import { Badge } from "@/src/components/Badge";
import { CopyButton } from "@/src/components/CopyButton/CopyButton";
import { CopyButton } from "@/src/components/IconButton/CopyButton";
import { Problem } from "@/src/types";
import { formatTime } from "@/src/utils/formatTime";
import { getPathname } from "@/src/utils/getPathname";
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ export const metadata: Metadata = {
export const RootLayout: FC<PropsWithChildren> = async ({ children }) => {
const pathParts = getPathname().split("/");

const facultyCode = pathParts.length >= 2 ? pathParts[2] : "";
const facultyCode = pathParts.length > 2 ? pathParts[2] : "";
const currentFaculty_ = await getFacultyByCode(facultyCode);
const currentFaculty =
currentFaculty_ instanceof Error ? undefined : currentFaculty_;

const courseCode = pathParts.length >= 4 ? pathParts[4] : "";
const courseCode = pathParts.length > 4 ? pathParts[4] : "";
const currentCourse_ = await getCourseByCode(courseCode);
const currentCourse =
currentCourse_ instanceof Error ? undefined : currentCourse_;

const assignmentCode = pathParts.length >= 6 ? pathParts[6] : "";
const assignmentCode = pathParts.length > 6 ? pathParts[6] : "";
const currentAssignment_ = await getAssignmentByCode(assignmentCode);
const currentAssignment =
currentAssignment_ instanceof Error ? undefined : currentAssignment_;

const recordID = pathParts.length >= 8 ? pathParts[8] : "";
const recordID = pathParts.length > 8 ? pathParts[8] : "";
const currentRecord_ = await getRecordByID(recordID);
const currentRecord =
currentRecord_ instanceof Error ? undefined : currentRecord_;
Expand Down
41 changes: 38 additions & 3 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
export default function Home() {
return <></>;
}
import { FaHouse } from "react-icons/fa6";
import { getAllFaculty } from "../api/faculty";
import { HowTo } from "./HowTo";
import { Intro } from "./Intro";
import { RecentCourses } from "./RecentCourses";

const Home = async () => {
const faculties = await getAllFaculty();
if (faculties instanceof Error) {
return <div>Error: {faculties.message}</div>;
}

return (
<main>
<div className="mx-auto max-w-[1440px] md:w-4/5">
<div className="m-4 flex flex-col gap-4 lg:mx-0 lg:my-6">
<div className="flex items-center gap-2">
<FaHouse className="h-6 w-6 text-secondary-default" />
<div className="text-2xl font-semibold text-high max-lg:text-xl">
Home
</div>
</div>
<RecentCourses faculties={faculties} />
<div className="flex w-full flex-row items-center justify-between gap-4 max-lg:flex-col">
<div className="flex w-[calc(50%-4px)] flex-col gap-2 rounded-lg bg-white p-3 max-lg:w-full lg:min-h-[356px] lg:py-4">
<Intro />
</div>
<div className="flex w-[calc(50%-4px)] flex-col gap-2 rounded-lg bg-white p-3 max-lg:w-full lg:min-h-[356px] lg:py-4">
<HowTo />
</div>
</div>
</div>
</div>
</main>
);
};

export default Home;
40 changes: 40 additions & 0 deletions frontend/src/cache/localStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
class LocalStorageManager {
setItem(key: string, value: object, ttl: number) {
const now = new Date();

const item = {
value: value,
expiry: now.getTime() + ttl, // Current time + TTL (in milliseconds)
};

localStorage.setItem(key, JSON.stringify(item));
}

getItem<T>(key: string): T | null {
const itemStr = localStorage.getItem(key);

if (!itemStr) {
return null;
}

const item = JSON.parse(itemStr);
const now = new Date();

if (now.getTime() > item.expiry) {
localStorage.removeItem(key);
return null;
}

return item.value;
}

deleteItem(key: string) {
localStorage.removeItem(key);
}

clearAll() {
localStorage.clear();
}
}

export const cache = new LocalStorageManager();
10 changes: 7 additions & 3 deletions frontend/src/components/Card/CourseCard.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
"use client";

import { setCurrentCourse } from "@/src/store/courseSlice";
import { pushRecentCourses, setCurrentCourse } from "@/src/store/courseSlice";
import { setCurrentFaculty } from "@/src/store/facultySlice";
import { useAppDispatch } from "@/src/store/store";
import { Course } from "@/src/types";
import { Course, Faculty } from "@/src/types";
import Image from "next/image";
import { FC } from "react";
import { Card } from ".";

interface CourseCardProps {
href: string;
course: Course;
faculty?: Faculty;
}

export const CourseCard: FC<CourseCardProps> = ({ href, course }) => {
export const CourseCard: FC<CourseCardProps> = ({ href, course, faculty }) => {
const { code, name, count } = course;
const dispatch = useAppDispatch();

const handleClick = () => {
if (faculty) dispatch(setCurrentFaculty(faculty));
dispatch(setCurrentCourse(course));
dispatch(pushRecentCourses(course));
};

return (
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/IconButton/ExtensionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";
import { FaExternalLinkAlt } from "react-icons/fa";

import Link from "next/link";
import { FC } from "react";

interface ExtensionButtonProps {
url: string;
}

export const ExtensionButton: FC<ExtensionButtonProps> = ({ url }) => {
return (
<Link
href={url}
target="_blank"
className="rounded-full p-2 text-medium hover:bg-primary-bg hover:text-primary-default"
>
<FaExternalLinkAlt className="outline-none" size={20} />
</Link>
);
};
23 changes: 23 additions & 0 deletions frontend/src/components/NavBar/NavItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,38 @@ import { FC, PropsWithChildren } from "react";
interface NavItemProps extends PropsWithChildren {
isSelected: boolean;
isEnabled: boolean;
isMobile: boolean;
href: string;
text: string;
children: React.ReactNode;
}

export const NavItem: FC<NavItemProps> = ({
isEnabled,
isSelected,
isMobile,
href,
text,
children,
}) => {
if (isMobile)
return (
<Link
href={isEnabled ? href : "#"}
className={clsx(
"h5 hover:bg-default flex items-center gap-2 rounded-lg px-2 py-3 text-high",
isSelected ? "bg-default" : "bg-white"
)}
>
{isEnabled && (
<>
{children}
{text}
</>
)}
</Link>
);

return (
<Link
href={isEnabled ? href : "#"}
Expand All @@ -34,6 +56,7 @@ export const NavItem: FC<NavItemProps> = ({
)}
>
{children}
{text}
</div>
</Link>
);
Expand Down
Loading

0 comments on commit 0e3d4c5

Please sign in to comment.