diff --git a/app/courses/[courseId]/modules/[moduleId]/page.tsx b/app/courses/[courseId]/modules/[moduleId]/page.tsx index cc8e5b2..f948bb9 100644 --- a/app/courses/[courseId]/modules/[moduleId]/page.tsx +++ b/app/courses/[courseId]/modules/[moduleId]/page.tsx @@ -1,18 +1,19 @@ // app/courses/[courseId]/modules/[moduleId]/page.tsx -import ModuleDetail, { - ModuleDetailData, -} from "@/components/organisation/courses/ModuleDetail"; +import ModuleDetail from "@/components/organisation/courses/ModuleDetail"; +import { getAuthUser } from "@/lib/auth"; export default async function ModulePage({ params, }: { params: { courseId: string; moduleId: string }; }) { + const user = await getAuthUser(); + const isAdmin = user?.organisation?.role === "admin"; const { moduleId } = await params; return (
- +
); } diff --git a/app/courses/page.tsx b/app/courses/page.tsx index 7ab03f0..d91be7c 100644 --- a/app/courses/page.tsx +++ b/app/courses/page.tsx @@ -8,6 +8,9 @@ import { useState, useEffect } from "react"; export default function CoursesPage() { const { user } = useAuth(); const [courses, setCourses] = useState([]); + const [enrolled, setEnrolled] = useState([]); + const [other, setOther] = useState([]); + const [completed, setCompleted] = useState([]); if (!user || !user.hasCompletedOnboarding) { return null; @@ -16,9 +19,7 @@ export default function CoursesPage() { const isAdmin = user?.organisation?.role === "admin"; useEffect(() => { - // Fetch courses from API or database - async function fetchCourses() { - // Replace with actual API call + async function fetchAdminCourses() { const fetchedCourses = await fetch("/api/courses", { credentials: "include", }) @@ -30,9 +31,91 @@ export default function CoursesPage() { }); setCourses(fetchedCourses); } - fetchCourses(); - }, []); + async function fetchUserCourses() { + const userCourses = await fetch("/api/courses/all-user-courses", { + credentials: "include", + }) + .then((res) => res.json()) + .then((data) => { + setEnrolled(data.enrolled || []); + setOther(data.other || []); + setCompleted(data.completed || []); + }) + .catch((err) => { + console.error("Failed to fetch user courses:", err); + setEnrolled([]); + setOther([]); + setCompleted([]); + }); + } + if (isAdmin) { + fetchAdminCourses(); + } else { + fetchUserCourses(); + } + }, [isAdmin]); + + if (isAdmin) { + return ( + + ); + } + + return ( +
+
+

+ My Courses +

+ {enrolled.length > 0 ? ( + + ) : ( +

+ You’re not enrolled in any courses yet. +

+ )} +
- // 3) Render the client component that will show/hide admin buttons - return ; +
+

+ Available Courses +

+ {other.length > 0 ? ( + + ) : ( +

No other courses available.

+ )} +
+
+

+ Completed Courses +

+ {completed.length > 0 ? ( + + ) : ( +

No completed courses.

+ )} +
+
+ ); } diff --git a/components/organisation/courses/CourseCard.tsx b/components/organisation/courses/CourseCard.tsx index 6ab3e5f..9d47288 100644 --- a/components/organisation/courses/CourseCard.tsx +++ b/components/organisation/courses/CourseCard.tsx @@ -6,25 +6,173 @@ export interface Course { id: number; name: string; description?: string; + total_modules?: number; + completed_modules?: number; } interface Props { course: Course; isAdmin: boolean; + isEnrolled: boolean; + isCompleted: boolean; } -export default function CourseCard({ course, isAdmin }: Props) { +export default function CourseCard({ + course, + isAdmin, + isEnrolled, + isCompleted, +}: Props) { + const handleEnroll = async () => { + try { + const response = await fetch("/api/courses/enroll-course", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ courseId: course.id }), + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to enroll in course"); + } + + window.location.reload(); + } catch (error) { + console.error("Error enrolling in course:", error); + } + }; + + const handleUnEnroll = async () => { + try { + const response = await fetch("/api/courses/unenroll-course", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ courseId: course.id }), + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to unenroll from course"); + } + + window.location.reload(); + } catch (error) { + console.error("Error unenrolling from course:", error); + } + }; + + const markNotCompleted = async () => { + try { + const response = await fetch("/api/courses/uncomplete-course", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ courseId: course.id }), + credentials: "include", + }); + + if (!response.ok) { + throw new Error("Failed to unenroll from course"); + } + + window.location.reload(); + } catch (error) { + console.error("Error unenrolling from course:", error); + } + }; + + const handleCompleteCourse = async () => { + try { + const res = await fetch("/api/courses/complete-course", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ courseId: course.id }), + }); + if (!res.ok) throw new Error("Failed to complete course"); + window.location.reload(); + } catch (err) { + console.error(err); + alert("Could not mark course complete"); + } + }; + + const allDone = + course.total_modules && + course.completed_modules === course.total_modules && + course.total_modules > 0; + return (

{course.name}

{course.description?.slice(0, 100)}

+ {!isAdmin && isEnrolled && ( +

+ Progress:{" "} + + {course.completed_modules ?? 0} + + / + + {course.total_modules ?? 0} + {" "} + modules completed. +

+ )} + + {allDone && !isAdmin && !isCompleted && ( + + )} +
+ {!isAdmin && !isEnrolled && !isCompleted && ( + + )} + {!isAdmin && isEnrolled && !isCompleted && ( + + )} + {!isAdmin && isCompleted && !allDone && ( + + )} + {!isAdmin && isCompleted && allDone && ( + + )} {isAdmin && ( {mode === "edit" && ( diff --git a/components/organisation/courses/CourseList.tsx b/components/organisation/courses/CourseList.tsx index 12b0d58..49f74e2 100644 --- a/components/organisation/courses/CourseList.tsx +++ b/components/organisation/courses/CourseList.tsx @@ -6,9 +6,16 @@ import CourseCard, { Course } from "./CourseCard"; interface Props { courses: Course[]; isAdmin: boolean; + isEnrolled: boolean; + isCompleted: boolean; } -export default function CourseList({ courses, isAdmin }: Props) { +export default function CourseList({ + courses, + isAdmin, + isEnrolled, + isCompleted, +}: Props) { return (
{isAdmin && ( @@ -23,7 +30,13 @@ export default function CourseList({ courses, isAdmin }: Props) {
{courses.map((c) => ( - + ))}
diff --git a/components/organisation/courses/ModuleCard.tsx b/components/organisation/courses/ModuleCard.tsx index fc4fdb9..abb7ffd 100644 --- a/components/organisation/courses/ModuleCard.tsx +++ b/components/organisation/courses/ModuleCard.tsx @@ -2,6 +2,8 @@ import Link from "next/link"; import { useParams } from "next/navigation"; +import { useEffect } from "react"; +import { useState } from "react"; export interface Module { id: number; @@ -13,10 +15,38 @@ export interface Module { interface Props { module: Module; isEditMode: boolean; + isEnrolled: boolean; } -export default function ModuleCard({ module, isEditMode }: Props) { +export default function ModuleCard({ module, isEditMode, isEnrolled }: Props) { const { courseId } = useParams() as { courseId: string }; + const [moduleStatus, setModuleStatus] = useState(null); + + useEffect(() => { + async function checkModuleStatus() { + try { + const response = await fetch(`/api/courses/get-module-status`, { + credentials: "include", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ moduleId: module.id }), + }); + if (!response.ok) { + throw new Error("Failed to fetch module status"); + } + const data = await response.json(); + setModuleStatus(data.status || null); + } catch (error) { + console.error("Error fetching module status:", error); + setModuleStatus(null); + } + } + if (isEnrolled && !isEditMode) { + checkModuleStatus(); + } + }, [module.id]); return (
@@ -30,11 +60,22 @@ export default function ModuleCard({ module, isEditMode }: Props) {

- + {(isEnrolled || isEditMode) && ( + + + + )} + {!isEnrolled && !isEditMode && ( - + )} {isEditMode && (