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
74 changes: 74 additions & 0 deletions app/(routes)/courses/_components/CourseList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use client'
import React, { useEffect, useState } from 'react'
import axios from 'axios'
import Image from 'next/image'
import { ChartNoAxesColumnIncreasing } from 'lucide-react'

type Course = {
id: number
courseId: number
title: string
desc: string
level: string
bannerImage: string
tag: string
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Type mismatch: Course type has tag but schema has tags.

The Course type defines a tag: string field (line 14), but the CourseTable schema in config/schema.tsx defines tags: varchar() (plural). This inconsistency could cause issues if you try to access course.tag when the API returns tags.

Apply this diff to match the schema:

 type Course = {
   id: number
   courseId: number
   title: string
   desc: string
   level: string
   bannerImage: string
-  tag: string
+  tags: string
 }
🤖 Prompt for AI Agents
In app/(routes)/courses/_components/CourseList.tsx around line 14, the Course
type declares a singular field `tag: string` while the DB/schema defines `tags`
(plural); update the type to match the schema by replacing `tag: string` with
`tags: string[]` (or the correct array/nullable shape used in the schema) and
then update any usages in this file to reference `course.tags` (mapping or
joining as needed) so runtime access matches the API contract.

}

function CourseList() {
const [courseList, setCourseList] = useState<Course[]>([])
const [loading, setLoading] = useState(true)

useEffect(() => {
GetAllCourses()
}, [])

const GetAllCourses = async () => {
const result = await axios.get('/api/course')
setCourseList(result?.data)
setLoading(false)
}
Comment on lines +25 to +29
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for the API call.

The GetAllCourses function doesn't handle potential errors from the axios request. Network failures or API errors will result in unhandled promise rejections.

Apply this diff:

   const GetAllCourses = async () => {
-    const result = await axios.get('/api/course')
-    setCourseList(result?.data)
-    setLoading(false)
+    try {
+      const result = await axios.get('/api/course')
+      setCourseList(result?.data)
+    } catch (error) {
+      console.error('Error fetching courses:', error)
+      // Optionally set an error state to show user feedback
+    } finally {
+      setLoading(false)
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const GetAllCourses = async () => {
const result = await axios.get('/api/course')
setCourseList(result?.data)
setLoading(false)
}
const GetAllCourses = async () => {
try {
const result = await axios.get('/api/course')
setCourseList(result?.data)
} catch (error) {
console.error('Error fetching courses:', error)
// Optionally set an error state to show user feedback
} finally {
setLoading(false)
}
}
🤖 Prompt for AI Agents
In app/(routes)/courses/_components/CourseList.tsx around lines 25–29, wrap the
axios.get call in a try/catch/finally: move the request into try,
setCourseList(result?.data) on success, catch errors to log them and/or show a
user-facing toast or set an error state, and ensure setLoading(false) runs in
the finally block so loading is cleared on both success and failure; optionally
keep courseList as an empty array on error.



return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-8 mt-6">

{courseList?.map((course, index) => (
<div
key={index}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use unique courseId as key instead of array index.

Using the array index as a React key can cause issues with component state and reconciliation, especially if the list order changes. Use the unique course.courseId or course.id instead.

Apply this diff:

-      {courseList?.map((course, index) => (
+      {courseList?.map((course) => (
         <div
-          key={index}
+          key={course.courseId}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In app/(routes)/courses/_components/CourseList.tsx around line 37, the component
currently uses the array index as the React key (key={index}); replace this with
a stable unique identifier from the course object such as key={course.courseId}
(or key={course.id} if courseId is not present). If necessary, use a nullish
fallback like course.courseId ?? course.id to ensure a stable unique
string/number key for each item.

className="rounded-2xl border-6 border-zinc-800 bg-zinc-900
shadow-lg overflow-hidden hover:-translate-y-2
hover:shadow-2xl transform transition-all duration-300 cursor-pointer"
>


{ <Image
src={course.bannerImage}
width={500}
height={300}
alt={course.title}
className="w-full h-[220px] object-cover"
/>}
Comment on lines +44 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix syntax: Remove commented opening brace.

Line 44 has a commented-out opening curly brace { before the Image component, which appears to be a typo or leftover from editing.

Apply this diff:

-          { <Image
+          <Image
             src={course.bannerImage}
             width={500}
             height={300}
             alt={course.title}
             className="w-full h-[220px] object-cover"
-          />}
+          />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{ <Image
src={course.bannerImage}
width={500}
height={300}
alt={course.title}
className="w-full h-[220px] object-cover"
/>}
<Image
src={course.bannerImage}
width={500}
height={300}
alt={course.title}
className="w-full h-[220px] object-cover"
/>
🤖 Prompt for AI Agents
In app/(routes)/courses/_components/CourseList.tsx around lines 44 to 50 there
is a stray commented opening curly brace before the Image component causing a
syntax/typo; remove the extra "{" so the Image JSX starts directly with <Image
... /> and ensure the surrounding JSX remains properly balanced (no leftover
comment or unmatched braces).


{/* Content */}
<div className="p-5 space-y-3">
<h2 className="font-game text-2xl">{course.title}</h2>

<p className="font-game text-lg text-gray-400 line-clamp-2">
{course.desc}
</p>

{/* Level Tag */}
<div className="flex items-center gap-2 bg-zinc-800 w-fit py-1 px-3 rounded-full">
<ChartNoAxesColumnIncreasing className="w-4 h-4 text-yellow-400" />
<span className="font-game text-sm text-gray-300">{course.level}</span>
</div>

</div>

</div>
))}
</div>
)
}

export default CourseList
46 changes: 46 additions & 0 deletions app/(routes)/courses/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import Image from "next/image";
import CourseList from './_components/CourseList';
import Header from '@/app/_components/Header';

function Courses() {
return (
<div className="bg-black min-h-screen">

{/* HEADER — Keep it OUTSIDE the relative banner */}
<div className="bg-black border-b border-zinc-800 font-game">
<div className="max-w-7xl mx-auto px-6 font-game">
<Header />
</div>
</div>

{/* BANNER */}
<div className="relative w-full">
<Image
src="/course-banner.gif"
alt="courseBanner"
width={1920}
height={400}
className="w-full h-[320px] object-cover"
/>

{/* BANNER TEXT OVERLAY */}
<div className="absolute inset-0 bg-gradient-to-r from-black/70 to-transparent flex flex-col justify-center px-10 md:px-24 lg:px-36">
<h2 className="font-game text-5xl md:text-6xl text-white">Explore All Courses</h2>
<p className="font-game text-2xl md:text-3xl text-gray-200 mt-3">
Increase your skills with high-quality learning!
</p>
</div>
</div>

{/* COURSE SECTION */}
<div className="max-w-7xl mx-auto mt-10 px-10 md:px-24 lg:px-36">
<h2 className="font-game text-4xl mb-6">All Courses</h2>
<CourseList />
</div>

</div>
)
}

export default Courses
28 changes: 21 additions & 7 deletions app/(routes)/dashboard/_components/EnrolledCourses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,24 @@

import { Button } from "@/components/ui/button";
import Image from "next/image";
import Link from "next/link";
import React, { useState } from 'react';

function EnrolledCourses() {
const [enrolledCourses, setEnrolledCourses] = useState([]);

return (
<div className="mt-8">
<h2 className="font-game mb-3 text-3xl">Your Enrolled courses</h2>
<h2 className="font-game mb-3 text-3xl">Your Enrolled Courses</h2>

{enrolledCourses.length === 0 ? (
<div className="flex flex-col items-center gap-3 p-6 border-2 border-black rounded-2xl bg-zinc-900 shadow-[6px_6px_0_0_#000]">
<div className="
flex flex-col items-center text-center gap-4
p-8 border-2 border-black rounded-2xl
bg-zinc-900 shadow-[6px_6px_0_0_#000]
">

{/* ICON */}
<Image
src="/books.png"
alt="book"
Expand All @@ -21,14 +28,21 @@ function EnrolledCourses() {
className="rounded-md"
/>

<h2 className="text-white text-lg font-semibold font-game ">
{/* TEXT */}
<h2 className="text-white text-xl font-game font-semibold">
You don't have any enrolled courses
</h2>

<Button variant="pixel" size="lg" className="mt-2 font-game text-xl">
Browse All Courses
</Button>

{/* BUTTON */}
<Link href="/courses">
<Button
variant="pixel"
size="lg"
className="mt-2 font-game text-xl"
>
Browse All Courses
</Button>
</Link>
</div>
) : (
<div>
Expand Down
10 changes: 10 additions & 0 deletions app/api/course/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {db} from '@/config/db';
import { CourseTable } from '@/config/schema';
import { NextRequest, NextResponse } from 'next/server';


export async function GET(req:NextRequest){
const result = await db.select().from(CourseTable);

return NextResponse.json(result)
}
Comment on lines +6 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling to prevent unhandled rejections.

The database query has no try-catch block. If the database connection fails or the query throws an error, it will result in an unhandled promise rejection and a generic 500 error response.

Apply this diff to add proper error handling:

 export async function GET(req:NextRequest){
-    const result =  await db.select().from(CourseTable);
-
-    return NextResponse.json(result)
+    try {
+        const result = await db.select().from(CourseTable);
+        return NextResponse.json(result);
+    } catch (error) {
+        console.error('Error fetching courses:', error);
+        return NextResponse.json(
+            { error: 'Failed to fetch courses' },
+            { status: 500 }
+        );
+    }
 }
🤖 Prompt for AI Agents
In app/api/course/route.ts around lines 6 to 10, the GET handler performs a DB
query without error handling; wrap the await db.select().from(CourseTable) call
in a try-catch, log the caught error (console.error or a project logger), and
return a NextResponse with a clear error message and status 500 inside the catch
block so unhandled rejections are prevented and clients receive a proper error
response.

11 changes: 11 additions & 0 deletions config/schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,14 @@ export const usersTable = pgTable("users", {
points: integer().default(0),
subscription: varchar()
});


export const CourseTable = pgTable("courses",{
id: integer().primaryKey().generatedAlwaysAsIdentity(),
courseId: integer().notNull().unique(),
title: varchar().notNull(),
desc: varchar().notNull(),
bannerImage: varchar().notNull(),
level: varchar().default('Beginner'),
tags: varchar()
})
4 changes: 3 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
images:{
domains:['ik.imagekit.io']
}
Comment on lines +4 to +6
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Migrate to remotePatterns instead of deprecated domains.

Next.js 15 deprecated the images.domains configuration in favor of remotePatterns, which provides more granular control and better security. The current configuration will still work but is considered legacy.

Apply this diff to use the modern configuration:

-  images:{
-    domains:['ik.imagekit.io']
-  }
+  images: {
+    remotePatterns: [
+      {
+        protocol: 'https',
+        hostname: 'ik.imagekit.io',
+      },
+    ],
+  }

Based on Next.js 15 documentation.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
images:{
domains:['ik.imagekit.io']
}
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'ik.imagekit.io',
},
],
}
🤖 Prompt for AI Agents
In next.config.ts around lines 4 to 6, the config uses the deprecated
images.domains setting; replace it with images.remotePatterns by removing
domains and adding a remotePatterns array containing an entry for host:
"ik.imagekit.io" (optionally include protocol: "https" and pathname: "/**" if
desired) so Next.js 15+ uses the modern granular image source pattern
configuration.

};

export default nextConfig;
Binary file added public/course-banner.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.