Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31fc258
Clean code
lavanyagarg112 Jun 23, 2025
6d5f463
Get enrolled and unenrolled courses
lavanyagarg112 Jun 23, 2025
e7ff970
Add isenrolled prop
lavanyagarg112 Jun 23, 2025
37e2b5f
Add api endpoint call to enroll user
lavanyagarg112 Jun 23, 2025
dce05e6
Add unenrolled endpoint api call
lavanyagarg112 Jun 23, 2025
0ebe913
Add enrollment requirement for module list
lavanyagarg112 Jun 23, 2025
8717b93
Add enrollment requirement for module
lavanyagarg112 Jun 23, 2025
eea5617
fix bug of finding enrollment by course id
lavanyagarg112 Jun 23, 2025
2368129
improve spacing in courses page
lavanyagarg112 Jun 23, 2025
9c3c2e0
Merge pull request #28 from lavanyagarg112/lavanya/enroll-courses
lavanyagarg112 Jun 23, 2025
f4a56a9
Store quiz response
lavanyagarg112 Jun 23, 2025
50d90a6
Clean up code
lavanyagarg112 Jun 24, 2025
371e55f
Display quiz results
lavanyagarg112 Jun 24, 2025
997d141
Add endpoint call to show latest results for quiz
lavanyagarg112 Jun 24, 2025
dde1102
Merge pull request #29 from lavanyagarg112/lavanya/attempt-quiz
lavanyagarg112 Jun 24, 2025
dd6fb0d
Add backend call to check for user status
lavanyagarg112 Jun 25, 2025
ec83393
Add functionality to update module status
lavanyagarg112 Jun 25, 2025
d88015e
update colour of mark module as completed
lavanyagarg112 Jun 25, 2025
4224035
Dont show Module enrollments for Admin
lavanyagarg112 Jun 25, 2025
081ad4b
Show course progress on course card
lavanyagarg112 Jun 25, 2025
cda3215
Add button to mark course as completed
lavanyagarg112 Jun 25, 2025
d80262c
display completed courses
lavanyagarg112 Jun 25, 2025
d7f56d4
Allow courses to be unenrolled and check for new modules after comple…
lavanyagarg112 Jun 25, 2025
0c96d50
Manage permissions if course completed
lavanyagarg112 Jun 25, 2025
1dcbf4d
refactor form to handle delete separately
lavanyagarg112 Jun 25, 2025
c8bd6aa
Merge pull request #30 from lavanyagarg112/lavanya/track-progress
lavanyagarg112 Jun 25, 2025
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
9 changes: 5 additions & 4 deletions app/courses/[courseId]/modules/[moduleId]/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="max-w-3xl mx-auto bg-white p-6 rounded shadow">
<ModuleDetail moduleId={moduleId} />
<ModuleDetail moduleId={moduleId} isAdmin={isAdmin} />
</div>
);
}
97 changes: 90 additions & 7 deletions app/courses/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import { useState, useEffect } from "react";
export default function CoursesPage() {
const { user } = useAuth();
const [courses, setCourses] = useState<Course[]>([]);
const [enrolled, setEnrolled] = useState<Course[]>([]);
const [other, setOther] = useState<Course[]>([]);
const [completed, setCompleted] = useState<Course[]>([]);

if (!user || !user.hasCompletedOnboarding) {
return null;
Expand All @@ -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",
})
Expand All @@ -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 (
<CourseList
courses={courses}
isAdmin={true}
isEnrolled={true}
isCompleted={true}
/>
);
}

return (
<div className="space-y-8">
<section>
<h2 className="text-2xl font-semibold text-purple-600 mb-4">
My Courses
</h2>
{enrolled.length > 0 ? (
<CourseList
courses={enrolled}
isAdmin={false}
isEnrolled={true}
isCompleted={false}
/>
) : (
<p className="text-gray-600">
You’re not enrolled in any courses yet.
</p>
)}
</section>

// 3) Render the client component that will show/hide admin buttons
return <CourseList courses={courses} isAdmin={isAdmin} />;
<section>
<h2 className="text-2xl font-semibold text-purple-600 mb-4">
Available Courses
</h2>
{other.length > 0 ? (
<CourseList
courses={other}
isAdmin={false}
isEnrolled={false}
isCompleted={false}
/>
) : (
<p className="text-gray-600">No other courses available.</p>
)}
</section>
<section>
<h2 className="text-2xl font-semibold text-purple-600 mb-4">
Completed Courses
</h2>
{completed.length > 0 ? (
<CourseList
courses={completed}
isAdmin={false}
isEnrolled={false}
isCompleted={true}
/>
) : (
<p className="text-gray-600">No completed courses.</p>
)}
</section>
</div>
);
}
150 changes: 149 additions & 1 deletion components/organisation/courses/CourseCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className="bg-white border rounded-lg p-6 shadow-sm hover:shadow-md transition">
<h2 className="text-xl font-semibold text-purple-600">{course.name}</h2>
<p className="mt-2 text-gray-700">{course.description?.slice(0, 100)}</p>

{!isAdmin && isEnrolled && (
<p className="mt-3 text-sm text-gray-600">
Progress:{" "}
<span className="font-medium text-gray-800">
{course.completed_modules ?? 0}
</span>
/
<span className="font-medium text-gray-800">
{course.total_modules ?? 0}
</span>{" "}
modules completed.
</p>
)}

{allDone && !isAdmin && !isCompleted && (
<button
onClick={handleCompleteCourse}
className="mt-4 px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700"
>
Mark Course as Completed
</button>
)}

<div className="mt-4 flex items-center space-x-4">
<Link href={`/courses/${course.id}`}>
<button className="text-purple-600 hover:underline">
View Modules
</button>
</Link>
{!isAdmin && !isEnrolled && !isCompleted && (
<button
className="text-purple-600 hover:underline"
onClick={handleEnroll}
>
Enroll
</button>
)}
{!isAdmin && isEnrolled && !isCompleted && (
<button
className="text-purple-600 hover:underline"
onClick={handleUnEnroll}
>
UnEnroll
</button>
)}
{!isAdmin && isCompleted && !allDone && (
<button
className="text-red-600 hover:underline"
onClick={markNotCompleted}
>
New modules added, click to continue Course
</button>
)}
{!isAdmin && isCompleted && allDone && (
<button
className="text-red-600 hover:underline"
onClick={markNotCompleted}
>
Mark as Not Completed
</button>
)}
{isAdmin && (
<Link href={`/courses/${course.id}/edit`}>
<button className="px-3 py-1 border border-purple-600 rounded text-purple-600 hover:bg-purple-50">
Expand Down
Loading