From 46700117e9bc630bad4caa1b42f82a63ca9a4fa2 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:21:48 +0700 Subject: [PATCH 01/19] EXTENSION_URL --- frontend/.env.template | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/.env.template b/frontend/.env.template index bbbd2e6..c70feef 100644 --- a/frontend/.env.template +++ b/frontend/.env.template @@ -1,4 +1,5 @@ NEXT_PUBLIC_COURSE_TTL=3600000 # 1 hour NEXT_PUBLIC_FACULTY_TTL=86400000 # 1 day API_URL=http://localhost:5203 -API_KEY=apikey \ No newline at end of file +API_KEY=apikey +EXTENSION_URL= \ No newline at end of file From 58287fcb7727547080b26d44a1ee07902c12f253 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:22:18 +0700 Subject: [PATCH 02/19] icon buttons --- .../[assignmentcode]/record/[id]/page.tsx | 2 +- .../{CopyButton => IconButton}/CopyButton.tsx | 0 .../components/IconButton/ExtensionButton.tsx | 30 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) rename frontend/src/components/{CopyButton => IconButton}/CopyButton.tsx (100%) create mode 100644 frontend/src/components/IconButton/ExtensionButton.tsx diff --git a/frontend/src/app/faculty/[facultycode]/course/[coursecode]/assignment/[assignmentcode]/record/[id]/page.tsx b/frontend/src/app/faculty/[facultycode]/course/[coursecode]/assignment/[assignmentcode]/record/[id]/page.tsx index 828e70d..9a0241e 100644 --- a/frontend/src/app/faculty/[facultycode]/course/[coursecode]/assignment/[assignmentcode]/record/[id]/page.tsx +++ b/frontend/src/app/faculty/[facultycode]/course/[coursecode]/assignment/[assignmentcode]/record/[id]/page.tsx @@ -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"; diff --git a/frontend/src/components/CopyButton/CopyButton.tsx b/frontend/src/components/IconButton/CopyButton.tsx similarity index 100% rename from frontend/src/components/CopyButton/CopyButton.tsx rename to frontend/src/components/IconButton/CopyButton.tsx diff --git a/frontend/src/components/IconButton/ExtensionButton.tsx b/frontend/src/components/IconButton/ExtensionButton.tsx new file mode 100644 index 0000000..2dd0ef2 --- /dev/null +++ b/frontend/src/components/IconButton/ExtensionButton.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { toast } from "@/hooks/use-toast"; +import { FC } from "react"; +import { FaCopy } from "react-icons/fa"; + +interface ExtensionButtonProps { + text: string; +} + +export const ExtensionButton: FC = ({ text }) => { + const handleClick = () => { + navigator.clipboard.writeText(text); + + toast({ + title: "Copied", + description: text, + }); + }; + + return ( + + ); +}; From 678608d53de0b8ffa46826e2300f674e1d4e9bb1 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:31:43 +0700 Subject: [PATCH 03/19] get extension btn --- .github/workflows/ext-ci.yml | 2 +- .../components/IconButton/ExtensionButton.tsx | 29 +++++++------------ frontend/src/components/NavBar/index.tsx | 7 ++++- frontend/src/config/config.ts | 2 ++ 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ext-ci.yml b/.github/workflows/ext-ci.yml index 6af3676..81dbfcc 100644 --- a/.github/workflows/ext-ci.yml +++ b/.github/workflows/ext-ci.yml @@ -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 diff --git a/frontend/src/components/IconButton/ExtensionButton.tsx b/frontend/src/components/IconButton/ExtensionButton.tsx index 2dd0ef2..cbbff22 100644 --- a/frontend/src/components/IconButton/ExtensionButton.tsx +++ b/frontend/src/components/IconButton/ExtensionButton.tsx @@ -1,30 +1,21 @@ "use client"; +import { FaExternalLinkAlt } from "react-icons/fa"; -import { toast } from "@/hooks/use-toast"; +import Link from "next/link"; import { FC } from "react"; -import { FaCopy } from "react-icons/fa"; interface ExtensionButtonProps { - text: string; + url: string; } -export const ExtensionButton: FC = ({ text }) => { - const handleClick = () => { - navigator.clipboard.writeText(text); - - toast({ - title: "Copied", - description: text, - }); - }; - +export const ExtensionButton: FC = ({ url }) => { return ( - + + ); }; diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index 14a07a7..fa42144 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -5,9 +5,11 @@ import { FaFileSignature, FaGraduationCap, FaHouse } from "react-icons/fa6"; import { usePathname } from "next/navigation"; +import { EXTENSION_URL } from "@/src/config/config"; import { selectCurrentCourse } from "@/src/store/courseSlice"; import { selectCurrentFaculty } from "@/src/store/facultySlice"; import { useAppSelector } from "@/src/store/store"; +import { ExtensionButton } from "../IconButton/ExtensionButton"; import { Logo } from "./Logo"; import { NavItem } from "./NavItem"; @@ -58,7 +60,10 @@ export const NavBar = () => { Assignments -
+
+ +

Get extension

+
); }; diff --git a/frontend/src/config/config.ts b/frontend/src/config/config.ts index 0a8e6f5..b6c9084 100644 --- a/frontend/src/config/config.ts +++ b/frontend/src/config/config.ts @@ -1,5 +1,7 @@ export const API_URL = process.env.API_URL; export const API_KEY = process.env.API_KEY; +export const EXTENSION_URL = process.env.EXTENSION_URL ?? ""; + export const FACULTY_TTL = parseInt( process.env.NEXT_PUBLIC_FACULTY_TTL ?? "", 10 From 3e2fe0b15afb2134ad65559fb71aa0a046d5b569 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:40:18 +0700 Subject: [PATCH 04/19] home header --- frontend/src/app/page.tsx | 24 ++++++- frontend/tailwind.config.ts | 130 +++++++++++++++++++----------------- 2 files changed, 88 insertions(+), 66 deletions(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 6ff5373..904eb71 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,21 @@ -export default function Home() { - return <>; -} +import { FaHouse } from "react-icons/fa6"; + +const Home = () => { + return ( +
+
+
+
+ +
+ Home +
+
+
+
+
+
+ ); +}; + +export default Home; diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index 8a7f31d..a379f7a 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -1,70 +1,74 @@ import type { Config } from "tailwindcss"; const config: Config = { - darkMode: ["class"], - content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], + darkMode: ["class"], + content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], theme: { - extend: { - colors: { - default: 'var(--default)', - primary: { - default: 'var(--primary-default)', - medium: 'var(--primary-medium)', - bg: 'var(--primary-bg)', - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - default: 'var(--secondary-default)', - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - success: { - default: 'var(--success-default)' - }, - medium: 'var(--medium)', - dark: 'var(--dark)', - high: 'var(--high)', - light: 'var(--light)', - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - }, - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - } - } + extend: { + fontSize: { + "2xl": ["32px", { lineHeight: "44px" }], + xl: ["24px", { lineHeight: "32px" }], + }, + colors: { + default: "var(--default)", + primary: { + default: "var(--primary-default)", + medium: "var(--primary-medium)", + bg: "var(--primary-bg)", + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + default: "var(--secondary-default)", + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + success: { + default: "var(--success-default)", + }, + medium: "var(--medium)", + dark: "var(--dark)", + high: "var(--high)", + light: "var(--light)", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + "1": "hsl(var(--chart-1))", + "2": "hsl(var(--chart-2))", + "3": "hsl(var(--chart-3))", + "4": "hsl(var(--chart-4))", + "5": "hsl(var(--chart-5))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, }, plugins: [require("tailwindcss-animate")], }; From 3c30f3f7649683e4ba1cf398798134de4ecec8bd Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:46:52 +0700 Subject: [PATCH 05/19] recent courses ui --- frontend/src/app/page.tsx | 24 +++++++++++++++++++++++- frontend/tailwind.config.ts | 1 + 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 904eb71..27faab7 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,3 +1,4 @@ +import { FaChalkboard } from "react-icons/fa"; import { FaHouse } from "react-icons/fa6"; const Home = () => { @@ -11,7 +12,28 @@ const Home = () => { Home -
+
+
+
+
+
+ +
+ Recent +
+
+
+
+
+ recent courses +
+
+
+
diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts index a379f7a..43ffd0a 100644 --- a/frontend/tailwind.config.ts +++ b/frontend/tailwind.config.ts @@ -8,6 +8,7 @@ const config: Config = { fontSize: { "2xl": ["32px", { lineHeight: "44px" }], xl: ["24px", { lineHeight: "32px" }], + lg: ["20px", { lineHeight: "26px" }], }, colors: { default: "var(--default)", From 11119f5f6b28b5823c42d987794b09da011700af Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:52:50 +0700 Subject: [PATCH 06/19] ls cache --- frontend/src/cache/localStoage.ts | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 frontend/src/cache/localStoage.ts diff --git a/frontend/src/cache/localStoage.ts b/frontend/src/cache/localStoage.ts new file mode 100644 index 0000000..24cc403 --- /dev/null +++ b/frontend/src/cache/localStoage.ts @@ -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(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(); From cf7f1d8e29d183aeccf670a41bb02dba347b1294 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:53:16 +0700 Subject: [PATCH 07/19] useGetRecentCourses --- .../cache/{localStoage.ts => localStorage.ts} | 0 frontend/src/hooks/useGetRecentCourses.ts | 20 +++++++++++++++++++ frontend/src/store/courseSlice.ts | 15 +++++++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) rename frontend/src/cache/{localStoage.ts => localStorage.ts} (100%) create mode 100644 frontend/src/hooks/useGetRecentCourses.ts diff --git a/frontend/src/cache/localStoage.ts b/frontend/src/cache/localStorage.ts similarity index 100% rename from frontend/src/cache/localStoage.ts rename to frontend/src/cache/localStorage.ts diff --git a/frontend/src/hooks/useGetRecentCourses.ts b/frontend/src/hooks/useGetRecentCourses.ts new file mode 100644 index 0000000..8bfd9e0 --- /dev/null +++ b/frontend/src/hooks/useGetRecentCourses.ts @@ -0,0 +1,20 @@ +import { useEffect } from "react"; +import { cache } from "../cache/localStoage"; +import { selectRecentCourses, setRecentCourses } from "../store/courseSlice"; +import { useAppDispatch, useAppSelector } from "../store/store"; +import { Course } from "../types"; + +export const useGetRecentCourses = () => { + const dispatch = useAppDispatch(); + const recentCourses = useAppSelector(selectRecentCourses); + + useEffect(() => { + const res = cache.getItem("recentCourses"); + if (res) { + dispatch(setRecentCourses(res)); + return; + } + }, []); + + return { recentCourses }; +}; diff --git a/frontend/src/store/courseSlice.ts b/frontend/src/store/courseSlice.ts index 5032541..9f22866 100644 --- a/frontend/src/store/courseSlice.ts +++ b/frontend/src/store/courseSlice.ts @@ -5,11 +5,13 @@ import { RootState } from "./store"; interface CourseState { courses: Course[]; currentCourse: Course | null; + recentCourses: Course[]; } const initialState: CourseState = { courses: [], currentCourse: null, + recentCourses: [], }; export const courseSlice = createSlice({ @@ -22,12 +24,23 @@ export const courseSlice = createSlice({ setCurrentCourse: (state, action: PayloadAction) => { state.currentCourse = action.payload; }, + setRecentCourses: (state, action: PayloadAction) => { + state.recentCourses = action.payload; + }, + pushRecentCourses: (state, action: PayloadAction) => {}, }, }); -export const { setCourses, setCurrentCourse } = courseSlice.actions; +export const { + setCourses, + setCurrentCourse, + setRecentCourses, + pushRecentCourses, +} = courseSlice.actions; export const selectCourses = (state: RootState) => state.course.courses; export const selectCurrentCourse = (state: RootState) => state.course.currentCourse; +export const selectRecentCourses = (state: RootState) => + state.course.recentCourses; export default courseSlice.reducer; From 6f2c54e2f6bc837662263aacc83d1ccfd201132d Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:53:16 +0700 Subject: [PATCH 08/19] s --- frontend/src/hooks/useGetRecentCourses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/useGetRecentCourses.ts b/frontend/src/hooks/useGetRecentCourses.ts index 8bfd9e0..66221aa 100644 --- a/frontend/src/hooks/useGetRecentCourses.ts +++ b/frontend/src/hooks/useGetRecentCourses.ts @@ -1,5 +1,5 @@ import { useEffect } from "react"; -import { cache } from "../cache/localStoage"; +import { cache } from "../cache/localStorage"; import { selectRecentCourses, setRecentCourses } from "../store/courseSlice"; import { useAppDispatch, useAppSelector } from "../store/store"; import { Course } from "../types"; From e6891d4e02057bdae49b895d4c9a4c4fe8c9933f Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 16:58:28 +0700 Subject: [PATCH 09/19] recent courses comp --- frontend/src/app/RecentCourses.tsx | 44 ++++++++++++++++++++++++++++++ frontend/src/app/page.tsx | 25 ++--------------- 2 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 frontend/src/app/RecentCourses.tsx diff --git a/frontend/src/app/RecentCourses.tsx b/frontend/src/app/RecentCourses.tsx new file mode 100644 index 0000000..b570927 --- /dev/null +++ b/frontend/src/app/RecentCourses.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { FaChalkboard } from "react-icons/fa6"; +import { CourseCard } from "../components/Card/CourseCard"; +import { useGetRecentCourses } from "../hooks/useGetRecentCourses"; + +export const RecentCourses = () => { + const { recentCourses } = useGetRecentCourses(); + + if (recentCourses.length === 0) { + return null; + } + + return ( +
+
+
+
+
+ +
+ Recent +
+
+
+
+
+ {recentCourses.map((c) => ( + + ))} +
+
+
+
+ ); +}; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 27faab7..05622f9 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,5 @@ -import { FaChalkboard } from "react-icons/fa"; import { FaHouse } from "react-icons/fa6"; +import { RecentCourses } from "./RecentCourses"; const Home = () => { return ( @@ -12,28 +12,7 @@ const Home = () => { Home
-
-
-
-
-
- -
- Recent -
-
-
-
-
- recent courses -
-
-
-
+
From dfe53aec3f521d12e213cb2d915c76b523c1baa6 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 17:09:54 +0700 Subject: [PATCH 10/19] recent courses push --- frontend/.env.template | 3 +-- frontend/src/components/Card/CourseCard.tsx | 3 ++- frontend/src/config/config.ts | 9 ++------- frontend/src/store/courseSlice.ts | 22 ++++++++++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/frontend/.env.template b/frontend/.env.template index c70feef..b4a2581 100644 --- a/frontend/.env.template +++ b/frontend/.env.template @@ -1,5 +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 EXTENSION_URL= \ No newline at end of file diff --git a/frontend/src/components/Card/CourseCard.tsx b/frontend/src/components/Card/CourseCard.tsx index b2c11b5..b9be789 100644 --- a/frontend/src/components/Card/CourseCard.tsx +++ b/frontend/src/components/Card/CourseCard.tsx @@ -1,6 +1,6 @@ "use client"; -import { setCurrentCourse } from "@/src/store/courseSlice"; +import { pushRecentCourses, setCurrentCourse } from "@/src/store/courseSlice"; import { useAppDispatch } from "@/src/store/store"; import { Course } from "@/src/types"; import Image from "next/image"; @@ -18,6 +18,7 @@ export const CourseCard: FC = ({ href, course }) => { const handleClick = () => { dispatch(setCurrentCourse(course)); + dispatch(pushRecentCourses(course)); }; return ( diff --git a/frontend/src/config/config.ts b/frontend/src/config/config.ts index b6c9084..240400f 100644 --- a/frontend/src/config/config.ts +++ b/frontend/src/config/config.ts @@ -2,12 +2,7 @@ export const API_URL = process.env.API_URL; export const API_KEY = process.env.API_KEY; export const EXTENSION_URL = process.env.EXTENSION_URL ?? ""; -export const FACULTY_TTL = parseInt( - process.env.NEXT_PUBLIC_FACULTY_TTL ?? "", - 10 -); - -export const COURSE_TTL = parseInt( - process.env.NEXT_PUBLIC_COURSE_TTL ?? "", +export const RECENT_COURSES_TTL = parseInt( + process.env.NEXT_PUBLIC_RECENT_COURSES_TTL ?? "", 10 ); diff --git a/frontend/src/store/courseSlice.ts b/frontend/src/store/courseSlice.ts index 9f22866..69126e9 100644 --- a/frontend/src/store/courseSlice.ts +++ b/frontend/src/store/courseSlice.ts @@ -1,4 +1,6 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { cache } from "../cache/localStorage"; +import { RECENT_COURSES_TTL } from "../config/config"; import { Course } from "../types"; import { RootState } from "./store"; @@ -27,7 +29,25 @@ export const courseSlice = createSlice({ setRecentCourses: (state, action: PayloadAction) => { state.recentCourses = action.payload; }, - pushRecentCourses: (state, action: PayloadAction) => {}, + pushRecentCourses: (state, action: PayloadAction) => { + // Prevent duplicate courses + const index = state.recentCourses.findIndex( + (course) => course.id === action.payload.id + ); + if (index !== -1) { + // Move the course to the top + state.recentCourses.splice(index, 1); + state.recentCourses = [action.payload, ...state.recentCourses]; + return; + } + + state.recentCourses = [action.payload, ...state.recentCourses]; + if (state.recentCourses.length > 3) { + state.recentCourses.pop(); + } + + cache.setItem("recentCourses", state.recentCourses, RECENT_COURSES_TTL); + }, }, }); From 46e7f3c8fa9bbfdea49a3a30c4581afb98a4f2f4 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 17:50:07 +0700 Subject: [PATCH 11/19] fix >= --- frontend/src/app/layout.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index 204841b..a6abe91 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -25,22 +25,22 @@ export const metadata: Metadata = { export const RootLayout: FC = 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_; From 7af8ca8523a4805bd1f4c6b19b9a4ac72d503e82 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 17:55:09 +0700 Subject: [PATCH 12/19] fix recent courses --- frontend/src/app/RecentCourses.tsx | 26 ++++++++++++++------- frontend/src/app/page.tsx | 10 ++++++-- frontend/src/components/Card/CourseCard.tsx | 7 ++++-- frontend/src/components/NavBar/index.tsx | 19 +++++++++++---- 4 files changed, 46 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/RecentCourses.tsx b/frontend/src/app/RecentCourses.tsx index b570927..b504c96 100644 --- a/frontend/src/app/RecentCourses.tsx +++ b/frontend/src/app/RecentCourses.tsx @@ -1,10 +1,16 @@ "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"; -export const RecentCourses = () => { +interface RecentCoursesProps { + faculties: Faculty[]; +} + +export const RecentCourses: FC = ({ faculties }) => { const { recentCourses } = useGetRecentCourses(); if (recentCourses.length === 0) { @@ -29,13 +35,17 @@ export const RecentCourses = () => { className="border-disable h-px border-b" />
- {recentCourses.map((c) => ( - - ))} + {recentCourses.map((c) => { + const faculty = faculties.find((f) => f.code === c.facultyCode); + return ( + + ); + })}
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 05622f9..de861c8 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,7 +1,13 @@ import { FaHouse } from "react-icons/fa6"; +import { getAllFaculty } from "../api/faculty"; import { RecentCourses } from "./RecentCourses"; -const Home = () => { +const Home = async () => { + const faculties = await getAllFaculty(); + if (faculties instanceof Error) { + return
Error: {faculties.message}
; + } + return (
@@ -12,7 +18,7 @@ const Home = () => { Home
- +
diff --git a/frontend/src/components/Card/CourseCard.tsx b/frontend/src/components/Card/CourseCard.tsx index b9be789..b5881dc 100644 --- a/frontend/src/components/Card/CourseCard.tsx +++ b/frontend/src/components/Card/CourseCard.tsx @@ -1,8 +1,9 @@ "use client"; 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 "."; @@ -10,13 +11,15 @@ import { Card } from "."; interface CourseCardProps { href: string; course: Course; + faculty?: Faculty; } -export const CourseCard: FC = ({ href, course }) => { +export const CourseCard: FC = ({ href, course, faculty }) => { const { code, name, count } = course; const dispatch = useAppDispatch(); const handleClick = () => { + if (faculty) dispatch(setCurrentFaculty(faculty)); dispatch(setCurrentCourse(course)); dispatch(pushRecentCourses(course)); }; diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index fa42144..ef808c1 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -15,6 +15,9 @@ import { NavItem } from "./NavItem"; export const NavBar = () => { const pathname = usePathname(); + const pathParts = pathname.split("/"); + const facultyCode = pathParts.length > 2 ? pathParts[2] : ""; + const courseCode = pathParts.length > 4 ? pathParts[4] : ""; const matchFaculty = pathname === "/faculty"; @@ -27,11 +30,19 @@ export const NavBar = () => { const currentFaculty = useAppSelector(selectCurrentFaculty); const currentCourse = useAppSelector(selectCurrentCourse); - const isCoursesEnabled = currentFaculty !== null; - const coursesPath = `/faculty/${currentFaculty?.code}/course`; + const facultyCode_ = currentFaculty?.code + ? currentFaculty?.code + : facultyCode; + const courseCode_ = currentCourse?.code ? currentCourse?.code : courseCode; - const isAssignmentsEnabled = isCoursesEnabled && currentCourse !== null; - const assignmentsPath = `/faculty/${currentFaculty?.code}/course/${currentCourse?.code}/assignment`; + const isCoursesEnabled = + currentFaculty?.code !== undefined || facultyCode !== ""; + const coursesPath = `/faculty/${facultyCode_}/course`; + + const isAssignmentsEnabled = + isCoursesEnabled && + (currentCourse?.code !== undefined || courseCode !== ""); + const assignmentsPath = `/faculty/${facultyCode_}/course/${courseCode_}/assignment`; return (
From 8eb7ed35d0fc4c0fa1215a6ef6f9415ca64a6696 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 18:02:26 +0700 Subject: [PATCH 13/19] how to scaffold --- frontend/src/app/HowTo.tsx | 23 +++++++++++++++++++++++ frontend/src/app/page.tsx | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 frontend/src/app/HowTo.tsx diff --git a/frontend/src/app/HowTo.tsx b/frontend/src/app/HowTo.tsx new file mode 100644 index 0000000..4004284 --- /dev/null +++ b/frontend/src/app/HowTo.tsx @@ -0,0 +1,23 @@ +import { FaListCheck } from "react-icons/fa6"; + +export const HowTo = () => { + return ( +
+
+
+
+ +
+ How to use extension +
+
+
+
+
+
+ ); +}; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index de861c8..3f21d97 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,5 +1,6 @@ import { FaHouse } from "react-icons/fa6"; import { getAllFaculty } from "../api/faculty"; +import { HowTo } from "./HowTo"; import { RecentCourses } from "./RecentCourses"; const Home = async () => { @@ -19,6 +20,7 @@ const Home = async () => {
+ From 0ca506339fb0488b9f14937d9d0cfe918ebecb31 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 18:22:02 +0700 Subject: [PATCH 14/19] intro --- frontend/src/app/HowTo.tsx | 37 ++++++++++++++++++++++++------------- frontend/src/app/Intro.tsx | 37 +++++++++++++++++++++++++++++++++++++ frontend/src/app/page.tsx | 10 +++++++++- 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 frontend/src/app/Intro.tsx diff --git a/frontend/src/app/HowTo.tsx b/frontend/src/app/HowTo.tsx index 4004284..94946a9 100644 --- a/frontend/src/app/HowTo.tsx +++ b/frontend/src/app/HowTo.tsx @@ -2,21 +2,32 @@ import { FaListCheck } from "react-icons/fa6"; export const HowTo = () => { return ( -
-
-
-
- -
- How to use extension -
+
+
+
+ +
+ How to use extension
-
+
+
+
+
  • Download and unzip the extension from this web
  • +
  • + Go to{" "} + chrome://extensions/{" "} + (you can copy and paste this link in your browser) +
  • +
  • + Click Load unpacked and + select the unzipped folder +
  • +
  • Now you can share your ideas with your friends
  • ); diff --git a/frontend/src/app/Intro.tsx b/frontend/src/app/Intro.tsx new file mode 100644 index 0000000..cc01bdc --- /dev/null +++ b/frontend/src/app/Intro.tsx @@ -0,0 +1,37 @@ +import { FaQuestion } from "react-icons/fa"; + +export const Intro = () => { + return ( +
    +
    +
    + +
    + What is OurCourseVille +
    +
    +
    +
    +
    +
  • Tired of taking screenshots of assignments?
  • +
  • + You can now "Share solution" of your assignment using our Chrome + extension +
  • +
  • Share once, learn anywhere
  • +
  • + We do NOT condone any form of cheating or plagiarism, we only provide + a platform for students to collaborate and share their ideas. +
  • +
  • + We do NOT collect any Personally Identifiable Information data from + you, we only collect solutions to the assignments. +
  • +
    +
    + ); +}; diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 3f21d97..db47809 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,6 +1,7 @@ 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 () => { @@ -20,7 +21,14 @@ const Home = async () => {
    - +
    +
    + +
    +
    + +
    +
    From 84b9b60acf14fb42efea297961f4fac146674f62 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 18:24:23 +0700 Subject: [PATCH 15/19] fix css --- frontend/src/app/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index db47809..99e42fb 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -25,7 +25,7 @@ const Home = async () => {
    -
    +
    From a256269006316b2344962f1059c33b517b381b03 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 20:48:16 +0700 Subject: [PATCH 16/19] remove scrollbar --- frontend/src/app/HowTo.tsx | 2 +- frontend/src/app/Intro.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/HowTo.tsx b/frontend/src/app/HowTo.tsx index 94946a9..bb283a8 100644 --- a/frontend/src/app/HowTo.tsx +++ b/frontend/src/app/HowTo.tsx @@ -16,7 +16,7 @@ export const HowTo = () => { role="separator" className="border-disable h-px border-b" /> -
    +
  • Download and unzip the extension from this web
  • Go to{" "} diff --git a/frontend/src/app/Intro.tsx b/frontend/src/app/Intro.tsx index cc01bdc..32d1f64 100644 --- a/frontend/src/app/Intro.tsx +++ b/frontend/src/app/Intro.tsx @@ -16,7 +16,7 @@ export const Intro = () => { role="separator" className="border-disable h-px border-b" /> -
    +
  • Tired of taking screenshots of assignments?
  • You can now "Share solution" of your assignment using our Chrome From f1534957d1ce21ef9d2a954352f6d4dec05be6d8 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 21:12:04 +0700 Subject: [PATCH 17/19] home repos --- frontend/src/components/NavBar/index.tsx | 11 +++++++++- frontend/src/hooks/useIsUnderLargeViewport.ts | 21 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 frontend/src/hooks/useIsUnderLargeViewport.ts diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index ef808c1..cfa3411 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -6,9 +6,11 @@ import { FaFileSignature, FaGraduationCap, FaHouse } from "react-icons/fa6"; import { usePathname } from "next/navigation"; import { EXTENSION_URL } from "@/src/config/config"; +import { useIsUnderLargeViewport } from "@/src/hooks/useIsUnderLargeViewport"; import { selectCurrentCourse } from "@/src/store/courseSlice"; import { selectCurrentFaculty } from "@/src/store/facultySlice"; import { useAppSelector } from "@/src/store/store"; +import clsx from "clsx"; import { ExtensionButton } from "../IconButton/ExtensionButton"; import { Logo } from "./Logo"; import { NavItem } from "./NavItem"; @@ -44,8 +46,15 @@ export const NavBar = () => { (currentCourse?.code !== undefined || courseCode !== ""); const assignmentsPath = `/faculty/${facultyCode_}/course/${courseCode_}/assignment`; + const { isUnderLarge } = useIsUnderLargeViewport(); + return ( -
    +
    diff --git a/frontend/src/hooks/useIsUnderLargeViewport.ts b/frontend/src/hooks/useIsUnderLargeViewport.ts new file mode 100644 index 0000000..3ac5be7 --- /dev/null +++ b/frontend/src/hooks/useIsUnderLargeViewport.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from "react"; + +export const useIsUnderLargeViewport = () => { + const [isUnderLarge, setIsUnderLarge] = useState( + () => window.innerWidth < 1024 + ); + + useEffect(() => { + const handleResize = () => { + setIsUnderLarge(window.innerWidth < 1024); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return { isUnderLarge }; +}; From fc51290ae7f2f10c5fa27a6bd13e2a79efb7e06c Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 21:59:50 +0700 Subject: [PATCH 18/19] hamburger --- frontend/bun.lockb | Bin 165017 -> 175318 bytes frontend/package.json | 1 + frontend/src/components/NavBar/NavItem.tsx | 23 ++++ frontend/src/components/NavBar/index.tsx | 124 +++++++++++++----- frontend/src/components/ui/popover.tsx | 33 +++++ frontend/src/hooks/useIsUnderLargeViewport.ts | 10 +- 6 files changed, 153 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/ui/popover.tsx diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 223d1046f08e6ffe621e179f40ee300cb55ebfa8..af4e4117ec8c6a015ba34a0950c42a61c533dcc2 100755 GIT binary patch delta 34672 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 EKbCDPr2qf` delta 29534 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 diff --git a/frontend/package.json b/frontend/package.json index 5438fbf..f8c7375 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/components/NavBar/NavItem.tsx b/frontend/src/components/NavBar/NavItem.tsx index b76413e..8cccaca 100644 --- a/frontend/src/components/NavBar/NavItem.tsx +++ b/frontend/src/components/NavBar/NavItem.tsx @@ -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 = ({ isEnabled, isSelected, + isMobile, href, + text, children, }) => { + if (isMobile) + return ( + + {isEnabled && ( + <> + {children} + {text} + + )} + + ); + return ( = ({ )} > {children} + {text}
    ); diff --git a/frontend/src/components/NavBar/index.tsx b/frontend/src/components/NavBar/index.tsx index cfa3411..0356522 100644 --- a/frontend/src/components/NavBar/index.tsx +++ b/frontend/src/components/NavBar/index.tsx @@ -1,17 +1,21 @@ "use client"; import { FaUniversity } from "react-icons/fa"; +import { FaBars } from "react-icons/fa6"; + import { FaFileSignature, FaGraduationCap, FaHouse } from "react-icons/fa6"; import { usePathname } from "next/navigation"; import { EXTENSION_URL } from "@/src/config/config"; -import { useIsUnderLargeViewport } from "@/src/hooks/useIsUnderLargeViewport"; +import { useIsMobileViewport } from "@/src/hooks/useIsUnderLargeViewport"; import { selectCurrentCourse } from "@/src/store/courseSlice"; import { selectCurrentFaculty } from "@/src/store/facultySlice"; import { useAppSelector } from "@/src/store/store"; import clsx from "clsx"; +import { useState } from "react"; import { ExtensionButton } from "../IconButton/ExtensionButton"; +import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Logo } from "./Logo"; import { NavItem } from "./NavItem"; @@ -46,44 +50,100 @@ export const NavBar = () => { (currentCourse?.code !== undefined || courseCode !== ""); const assignmentsPath = `/faculty/${facultyCode_}/course/${courseCode_}/assignment`; - const { isUnderLarge } = useIsUnderLargeViewport(); + const { isMobile } = useIsMobileViewport(); + + const nav = () => ( + <> + + + + + + + + + + + + + + ); + + const [isOpen, setIsOpen] = useState(false); + const toggleMenu = () => { + setIsOpen(!isOpen); + }; + const mobile = () => { + return ( + <> + + + + + +
    +
    +
    + Menu +
    +
    setIsOpen(false)}> + {nav()} +
    +
    +
    +
    +
    + + + + ); + }; + + const desktop = () => ( + <> + +
    {nav()}
    +
    + +

    Get extension

    +
    + + ); return (
    - -
    - - - Home - - - - Faculties - - - Courses - - - Assignments - -
    -
    - -

    Get extension

    -
    + {isMobile ? mobile() : desktop()}
    ); }; diff --git a/frontend/src/components/ui/popover.tsx b/frontend/src/components/ui/popover.tsx new file mode 100644 index 0000000..29c7bd2 --- /dev/null +++ b/frontend/src/components/ui/popover.tsx @@ -0,0 +1,33 @@ +"use client" + +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/frontend/src/hooks/useIsUnderLargeViewport.ts b/frontend/src/hooks/useIsUnderLargeViewport.ts index 3ac5be7..de59176 100644 --- a/frontend/src/hooks/useIsUnderLargeViewport.ts +++ b/frontend/src/hooks/useIsUnderLargeViewport.ts @@ -1,13 +1,11 @@ import { useEffect, useState } from "react"; -export const useIsUnderLargeViewport = () => { - const [isUnderLarge, setIsUnderLarge] = useState( - () => window.innerWidth < 1024 - ); +export const useIsMobileViewport = () => { + const [isMobile, setIsMobile] = useState(() => window.innerWidth < 1024); useEffect(() => { const handleResize = () => { - setIsUnderLarge(window.innerWidth < 1024); + setIsMobile(window.innerWidth < 1024); }; window.addEventListener("resize", handleResize); @@ -17,5 +15,5 @@ export const useIsUnderLargeViewport = () => { }; }, []); - return { isUnderLarge }; + return { isMobile }; }; From 62aa8f8152109828db298211867d5f3849557311 Mon Sep 17 00:00:00 2001 From: Idhibhat Pankam Date: Wed, 9 Oct 2024 22:02:31 +0700 Subject: [PATCH 19/19] " --- frontend/src/app/Intro.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/Intro.tsx b/frontend/src/app/Intro.tsx index 32d1f64..ce0d922 100644 --- a/frontend/src/app/Intro.tsx +++ b/frontend/src/app/Intro.tsx @@ -19,8 +19,8 @@ export const Intro = () => {
  • Tired of taking screenshots of assignments?
  • - You can now "Share solution" of your assignment using our Chrome - extension + You can now "Share solution" of your assignment using our + Chrome extension
  • Share once, learn anywhere