-
Notifications
You must be signed in to change notification settings - Fork 0
Chapter 8 Done #6
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 | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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 | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for the API call. The 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| 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} | ||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Use unique 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 Apply this diff: - {courseList?.map((course, index) => (
+ {courseList?.map((course) => (
<div
- key={index}
+ key={course.courseId}
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix syntax: Remove commented opening brace. Line 44 has a commented-out opening curly brace Apply this diff: - { <Image
+ <Image
src={course.bannerImage}
width={500}
height={300}
alt={course.title}
className="w-full h-[220px] object-cover"
- />}
+ />📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| {/* 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 | ||||||||||||||||||||||||||||||||||
| 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 |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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 |
||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Migrate to Next.js 15 deprecated the 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| export default nextConfig; | ||||||||||||||||||||||||
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.
Type mismatch: Course type has
tagbut schema hastags.The Course type defines a
tag: stringfield (line 14), but the CourseTable schema in config/schema.tsx definestags: varchar()(plural). This inconsistency could cause issues if you try to accesscourse.tagwhen the API returnstags.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