Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 34 additions & 2 deletions src/pages/api/enrollment/enroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,51 @@ import { requireAuth, AuthenticatedRequest } from '@/lib/rbac';
import prisma from '@/lib/prisma';

/**
* POST /api/enrollment/enroll
* GET /api/enrollment/enroll
* Fetch user's enrollments
*
* POST /api/enrollment/enroll
* Enroll user in a course
* Body: { courseId: string }
*/
export default requireAuth(async (req: AuthenticatedRequest, res: NextApiResponse) => {
const userId = req.user!.id;

// GET - Fetch user's enrollments
if (req.method === 'GET') {
try {
const enrollments = await prisma.enrollment.findMany({
where: { userId },
include: {
course: {
select: {
id: true,
title: true,
description: true,
imageUrl: true,
difficulty: true,
category: true,
},
},
},
orderBy: {
enrolledAt: 'desc',
},
});

return res.status(200).json({ enrollments });
} catch (error) {
console.error('Error fetching enrollments:', error);
return res.status(500).json({ error: 'Failed to fetch enrollments' });
}
}

if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}

try {
const { courseId } = req.body;
const userId = req.user!.id;

// Validation
if (!courseId) {
Expand Down
130 changes: 108 additions & 22 deletions src/pages/courses/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect, useState } from "react";
import Link from "next/link";
import Layout01 from "@layout/layout-01";
import type { GetServerSideProps, NextPage } from "next";
Expand All @@ -7,6 +7,13 @@ import { options } from "@/pages/api/auth/options";
import SEO from "@components/seo/page-seo";
import Breadcrumb from "@components/breadcrumb";

type Enrollment = {
id: string;
courseId: string;
status: string;
progress: number;
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Enrollment type definition doesn't match the API response structure. The API returns enrollments with a nested course object containing id, title, description, imageUrl, difficulty, and category fields. Update the type to include a course property with these fields.

Suggested change
progress: number;
progress: number;
course: {
id: string;
title: string;
description: string;
imageUrl: string;
difficulty: string;
category: string;
};

Copilot uses AI. Check for mistakes.
};

type PageProps = {
user: {
id: string;
Expand All @@ -27,6 +34,49 @@ type PageWithLayout = NextPage<PageProps> & {
};

const CoursesIndex: PageWithLayout = ({ user }) => {
const [enrollments, setEnrollments] = useState<Enrollment[]>([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
fetchEnrollments();
}, []);

const fetchEnrollments = async () => {
try {
setLoading(true);
const response = await fetch("/api/enrollment/enroll");
const data = await response.json();

if (response.ok) {
setEnrollments(data.enrollments);
}
} catch (error) {
console.error("Error fetching enrollments:", error);
} finally {
setLoading(false);
}
};

// Helper function to check if user is enrolled in a vertical
// Map vertical slugs to course titles for enrollment checking
const verticalCourseMap: Record<string, string[]> = {
"software-engineering": ["Software Engineering", "Full-Stack Development", "Web Development"],
"data-engineering": ["Data Engineering", "Data Science"],
"ai-engineering": ["AI Engineering", "Machine Learning", "Artificial Intelligence"],
"devops": ["DevOps", "Cloud Engineering"],
"web-development": ["Web Development", "Frontend Development"],
};

const isEnrolledInVertical = (verticalSlug: string): boolean => {
const matchingTitles = verticalCourseMap[verticalSlug] || [];
return enrollments.some(
(enrollment) =>
enrollment.status === "ACTIVE" &&
matchingTitles.some((title) =>
enrollment.courseId.toLowerCase().includes(title.toLowerCase())
)
);
};
Comment on lines +70 to +79
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enrollment check is comparing the courseId field with course title strings, but based on the API response structure, courseId is likely an ID (string), not a title. The comparison should use enrollment.course.title instead of enrollment.courseId to properly match against the title patterns in verticalCourseMap.

Copilot uses AI. Check for mistakes.

return (
<>
Expand Down Expand Up @@ -100,13 +150,21 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
{/* Software Engineering Vertical */}
<div className="tw-group tw-overflow-hidden tw-rounded-xl tw-border tw-border-gray-100 tw-bg-white tw-shadow-lg tw-transition-all tw-duration-300 hover:tw-scale-105 hover:tw-shadow-2xl">
<div className="tw-bg-gradient-to-br tw-from-primary tw-via-primary tw-to-primary/80 tw-p-8">
<div className="tw-mb-6 tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-code tw-text-3xl tw-text-white" />
<div className="tw-mb-6 tw-flex tw-items-center tw-justify-between">
<div className="tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-code tw-text-3xl tw-text-white" />
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
Software Engineering
</h3>
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
Software Engineering
</h3>
{!loading && isEnrolledInVertical("software-engineering") && (
<span className="tw-rounded-full tw-bg-white/90 tw-px-3 tw-py-1 tw-text-xs tw-font-semibold tw-text-primary">
<i className="fas fa-check-circle tw-mr-1" />
Enrolled
</span>
)}
</div>
<p className="tw-text-lg tw-leading-relaxed tw-text-white/95">
Master full-stack development, system design, and software
Expand Down Expand Up @@ -143,7 +201,11 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
href="/courses/software-engineering"
className="tw-group tw-flex tw-w-full tw-items-center tw-justify-center tw-rounded-lg tw-bg-primary tw-px-6 tw-py-4 tw-font-semibold tw-text-white tw-transition-all tw-duration-200 hover:tw-bg-primary/90 hover:tw-shadow-lg"
>
<span>View Vertical</span>
<span>
{!loading && isEnrolledInVertical("software-engineering")
? "Continue Learning"
: "View Vertical"}
</span>
<i className="fas fa-arrow-right tw-ml-2 tw-transition-transform tw-duration-200 group-hover:tw-translate-x-1" />
</Link>
</div>
Expand All @@ -152,13 +214,21 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
{/* Data Engineering Vertical */}
<div className="tw-group tw-overflow-hidden tw-rounded-xl tw-border tw-border-gray-100 tw-bg-white tw-shadow-lg tw-transition-all tw-duration-300 hover:tw-scale-105 hover:tw-shadow-2xl">
<div className="tw-bg-gradient-to-br tw-from-secondary tw-via-secondary tw-to-secondary/80 tw-p-8">
<div className="tw-mb-6 tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-database tw-text-3xl tw-text-white" />
<div className="tw-mb-6 tw-flex tw-items-center tw-justify-between">
<div className="tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-database tw-text-3xl tw-text-white" />
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
Data Engineering
</h3>
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
Data Engineering
</h3>
{!loading && isEnrolledInVertical("data-engineering") && (
<span className="tw-rounded-full tw-bg-white/90 tw-px-3 tw-py-1 tw-text-xs tw-font-semibold tw-text-secondary">
<i className="fas fa-check-circle tw-mr-1" />
Enrolled
</span>
)}
</div>
<p className="tw-text-lg tw-leading-relaxed tw-text-white/95">
Build data pipelines, work with big data technologies, and create
Expand Down Expand Up @@ -195,7 +265,11 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
href="/courses/data-engineering"
className="tw-group tw-flex tw-w-full tw-items-center tw-justify-center tw-rounded-lg tw-bg-secondary tw-px-6 tw-py-4 tw-font-semibold tw-text-white tw-transition-all tw-duration-200 hover:tw-bg-secondary/90 hover:tw-shadow-lg"
>
<span>View Vertical</span>
<span>
{!loading && isEnrolledInVertical("data-engineering")
? "Continue Learning"
: "View Vertical"}
</span>
<i className="fas fa-arrow-right tw-ml-2 tw-transition-transform tw-duration-200 group-hover:tw-translate-x-1" />
</Link>
</div>
Expand All @@ -204,13 +278,21 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
{/* AI Engineering Vertical */}
<div className="tw-group tw-overflow-hidden tw-rounded-xl tw-border tw-border-gray-100 tw-bg-white tw-shadow-lg tw-transition-all tw-duration-300 hover:tw-scale-105 hover:tw-shadow-2xl">
<div className="tw-bg-gradient-to-br tw-from-success tw-via-success tw-to-success/80 tw-p-8">
<div className="tw-mb-6 tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-brain tw-text-3xl tw-text-white" />
<div className="tw-mb-6 tw-flex tw-items-center tw-justify-between">
<div className="tw-flex tw-items-center">
<div className="tw-rounded-lg tw-bg-white/20 tw-p-3">
<i className="fas fa-brain tw-text-3xl tw-text-white" />
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
AI Engineering
</h3>
</div>
<h3 className="tw-ml-4 tw-text-2xl tw-font-bold tw-text-white">
AI Engineering
</h3>
{!loading && isEnrolledInVertical("ai-engineering") && (
<span className="tw-rounded-full tw-bg-white/90 tw-px-3 tw-py-1 tw-text-xs tw-font-semibold tw-text-success">
<i className="fas fa-check-circle tw-mr-1" />
Enrolled
</span>
)}
</div>
<p className="tw-text-lg tw-leading-relaxed tw-text-white/95">
Develop AI/ML models, work with neural networks, and build
Expand Down Expand Up @@ -247,7 +329,11 @@ const CoursesIndex: PageWithLayout = ({ user }) => {
href="/courses/ai-engineering"
className="tw-group tw-flex tw-w-full tw-items-center tw-justify-center tw-rounded-lg tw-bg-success tw-px-6 tw-py-4 tw-font-semibold tw-text-white tw-transition-all tw-duration-200 hover:tw-bg-success/90 hover:tw-shadow-lg"
>
<span>View Vertical</span>
<span>
{!loading && isEnrolledInVertical("ai-engineering")
? "Continue Learning"
: "View Vertical"}
</span>
<i className="fas fa-arrow-right tw-ml-2 tw-transition-transform tw-duration-200 group-hover:tw-translate-x-1" />
</Link>
</div>
Expand Down
80 changes: 79 additions & 1 deletion src/pages/courses/software-engineering.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import Layout01 from "@layout/layout-01";
import type { GetServerSideProps, NextPage } from "next";
Expand All @@ -7,6 +7,13 @@ import { options } from "@/pages/api/auth/options";
import SEO from "@components/seo/page-seo";
import Breadcrumb from "@components/breadcrumb";

type Enrollment = {
id: string;
courseId: string;
status: string;
progress: number;
Comment on lines 9 to +14
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Enrollment type definition doesn't match the API response structure. According to the API implementation in enroll.ts, enrollments include a nested course object with fields like id, title, description, imageUrl, difficulty, and category. The type should include a course property with these fields.

Suggested change
type Enrollment = {
id: string;
courseId: string;
status: string;
progress: number;
type CourseInfo = {
id: string;
title: string;
description: string;
imageUrl: string;
difficulty: string;
category: string;
};
type Enrollment = {
id: string;
courseId: string;
status: string;
progress: number;
course: CourseInfo;

Copilot uses AI. Check for mistakes.
};

type PageProps = {
user: {
id: string;
Expand Down Expand Up @@ -102,6 +109,47 @@ const modules = [

const SoftwareEngineeringCourse: PageWithLayout = ({ user: _user }) => {
const [selectedModule, setSelectedModule] = useState<number | null>(null);
const [enrollments, setEnrollments] = useState<Enrollment[]>([]);
const [loading, setLoading] = useState(true);
const [enrollmentError, setEnrollmentError] = useState<string | null>(null);

useEffect(() => {
fetchEnrollments();
}, []);

const fetchEnrollments = async () => {
try {
setLoading(true);
const response = await fetch("/api/enrollment/enroll");
const data = await response.json();

if (response.ok) {
setEnrollments(data.enrollments);
}
} catch (error) {
console.error("Error fetching enrollments:", error);
} finally {
setLoading(false);
}
};

// Check if enrolled in Software Engineering vertical
const isEnrolled = (): boolean => {
const matchingTitles = ["Software Engineering", "Full-Stack Development", "Web Development"];
return enrollments.some(
(enrollment) =>
enrollment.status === "ACTIVE" &&
matchingTitles.some((title) =>
enrollment.courseId.toLowerCase().includes(title.toLowerCase())
)
);
};
Comment on lines +137 to +146
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The enrollment check is comparing courseId with course title strings, but courseId is an ID field, not a title. Based on the API response that includes course details, this should use enrollment.course.title instead of enrollment.courseId.

Copilot uses AI. Check for mistakes.

const handleEnroll = async () => {
// For now, this is a placeholder since we don't have a courseId
// In a real implementation, you'd need to map the vertical to an actual course
setEnrollmentError("Enrollment is not yet configured for this vertical. Please contact an administrator.");
};

return (
<>
Expand Down Expand Up @@ -134,6 +182,36 @@ const SoftwareEngineeringCourse: PageWithLayout = ({ user: _user }) => {
</p>
</div>

{/* Enrollment Status/CTA */}
{!loading && (
<div className="tw-mb-8 tw-text-center">
{isEnrolled() ? (
<div className="tw-inline-flex tw-items-center tw-rounded-lg tw-bg-success/10 tw-px-6 tw-py-3 tw-text-success">
<i className="fas fa-check-circle tw-mr-2 tw-text-xl" />
<span className="tw-font-semibold">
You&apos;re enrolled in this vertical
</span>
</div>
) : (
<div>
<button
type="button"
onClick={handleEnroll}
className="tw-inline-flex tw-items-center tw-rounded-lg tw-bg-primary tw-px-8 tw-py-4 tw-text-lg tw-font-semibold tw-text-white tw-transition-all hover:tw-bg-primary/90 hover:tw-shadow-lg"
>
<i className="fas fa-user-plus tw-mr-2" />
Enroll in Vertical
</button>
{enrollmentError && (
<p className="tw-mt-3 tw-text-sm tw-text-red-600">
{enrollmentError}
</p>
)}
</div>
)}
</div>
)}

{/* Course Stats */}
<div className="tw-grid tw-grid-cols-2 tw-gap-6 tw-rounded-xl tw-bg-gradient-to-r tw-from-primary tw-to-primary/80 tw-p-8 tw-text-white md:tw-grid-cols-4">
<div className="tw-text-center">
Expand Down