-
-
Notifications
You must be signed in to change notification settings - Fork 68
feat: streamline course enrollment flow with status indicators and CTAs #849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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"; | ||
|
|
@@ -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; | ||
| }; | ||
|
|
||
| type PageProps = { | ||
| user: { | ||
| id: string; | ||
|
|
@@ -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
|
||
|
|
||
| return ( | ||
| <> | ||
|
|
@@ -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 | ||
|
|
@@ -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> | ||
|
|
@@ -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 | ||
|
|
@@ -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> | ||
|
|
@@ -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 | ||
|
|
@@ -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> | ||
|
|
||
| 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"; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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
|
||||||||||||||||||||||||||||||||||||||||||||||
| 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
AI
Jan 1, 2026
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
courseproperty with these fields.