Skip to content

Commit

Permalink
Merge branch 'staging' into staging
Browse files Browse the repository at this point in the history
  • Loading branch information
brueningf authored Feb 6, 2025
2 parents afc9850 + 2cfcf70 commit 454d5f4
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 205 deletions.
12 changes: 12 additions & 0 deletions backend/src/routes/courses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ router.get("/", async (req: Request, res: Response) => {
}
});

// GET /courses/popular
router.get("/popular", async (req: Request, res: Response) => {
try {
const courses = await getAllCourses(ctx);
const popularCourses = courses.slice(0, 3);
res.status(200).json(popularCourses);
} catch (error) {
console.error(error);
res.status(500).json({ error: "Failed to fetch popular courses." });
}
});

// GET /courses/:slug
router.get("/:slug", async (req: Request<{ slug: string }>, res: Response) => {
try {
Expand Down
44 changes: 44 additions & 0 deletions frontend/src/app/courses/CourseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import moment from "moment";
import Link from "next/link";
import { type Course } from "@/types";

export default function CourseCard({ course }: { course: Course }) {
return (
<Link
key={course.id}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 dark:bg-gray-800 bg-white"
data-testid="course"
href={`/courses/${course.slug}`}
>
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
{course.difficulty}
</div>
<span className="text-sm text-gray-500 dark:text-gray-400">
{moment.duration(course.duration).humanize()}
</span>
</div>
<div className="relative h-40 mb-2 overflow-hidden rounded-lg">
<img
src={course.thumbnail}
alt={course.title}
width={200}
height={200}
className="absolute inset-0 object-cover object-center w-full h-full"
/>
</div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
{course.title}
</h2>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
{course.shortDescription}
</p>

<div className="flex justify-between items-center mt-4">
<span className="text-sm text-gray-500 dark:text-gray-400">
{course?.instructor?.name}
</span>
</div>
</Link>
);
}
165 changes: 101 additions & 64 deletions frontend/src/app/courses/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,87 +80,124 @@ export default async function Courses({
};

return (
<div
key={course.id}
className="w-full p-6 border border-gray-200 dark:border-gray-700 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 dark:bg-gray-800 bg-white"
data-testid="course"
>
<div className="flex justify-between items-center mb-4">
<div className="w-full grid grid-cols-1 gap-y-4 p-6 border border-gray-200 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 bg-white">
{/* Header Section */}
<div className="flex justify-between items-start">
<div>
<h2 className="text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2">
<h2 className="text-2xl font-bold text-gray-900">
{course.title}
</h2>

<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">
{course.shortDescription}
</p>
</div>
</div>

<DynamicEnrollButton />
<div className="flex items-center justify-between">
<div className="flex items-center">
<img
src={course.instructor?.image || ""}
alt={course.instructor?.name || ""}
className="w-8 h-8 rounded-full"
/>
<span className="text-sm text-gray-600 ml-2">
{course.instructor.name}
</span>
</div>
<div className="flex items-center space-x-4 mb-4">
<span className="text-sm text-gray-600">
{course.difficulty}
</span>
<span className="text-sm text-gray-600">
{moment.duration(course.duration).humanize()}
</span>
</div>
</div>

<div className="flex justify-start items-center mb-4">
<span className="mr-3 text-sm text-gray-500 dark:text-gray-400">
{course.difficulty}
</span>
<span className="text-sm text-gray-500 dark:text-gray-400">
{moment.duration(course.duration).humanize()}
</span>
<div className="flex items-center justify-between">
<p className="text-sm text-gray-600 mt-2">
{course.shortDescription}
</p>
<DynamicEnrollButton />
</div>

<div className="md:flex items-start justify-between mb-4 gap-4">
<video controls className="rounded-lg shadow-sm max-h-40">
{/* Video & Description */}
<div className="grid grid-cols-2">
<video
controls
className="rounded-lg shadow-sm w-full md:w-2/3"
>
<source src={course.introVideoUrl} type="video/mp4" />
Your browser does not support the video tag.
</video>
<div className="flex flex-col items-center border p-6 rounded-lg">
<img
src={course.instructor?.image || "/avatar.png"}
alt={`${course.instructor?.name} Profile`}
className="w-16 h-16 rounded-full shadow"
/>
<span className="text-sm text-gray-500 dark:text-gray-400 mt-2">
{course.instructor?.name}
</span>
<span className="text-sm text-gray-500 dark:text-gray-400 mt-2">
{course.instructor?.bio}
</span>
</div>

{/* Course Description */}
<p className="text-sm text-gray-700 mb-6">
{course.description}
</p>
</div>

<Progress />

<p className="text-sm text-gray-500 dark:text-gray-400 mb-6">
{course.description}
</p>

<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-2">
Course Outline
</h3>
<ul className="list-disc list-inside text-sm text-black space-y-1">
{course.units?.map((unit: Unit, index: number) => (
<div key={unit.id} className="mb-4">
<div className="flex items-center mb-2">
<p>Unit {index + 1} - </p>
<p>
{moment.duration(unit.duration).humanize()}
</p>
<div className="grid grid-cols-2 text-black">
{/* Course Outline */}
<div>
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Course Outline
</h3>
<div className="space-y-4">
{course.units?.map((unit: Unit, index) => (
<div
key={unit.id}
className="p-4 border rounded-lg bg-gray-50"
>
<div className="flex justify-between items-center mb-2">
<h4 className="font-semibold">
Unit {index + 1} -{" "}
{moment
.duration(unit.duration)
.humanize()}
</h4>
</div>
<h3 className="text-lg font-semibold mb-2">
{unit.title}
</h3>
<p className="">{unit.description}</p>
{unit.assignments?.map(
(assignment: Assignment) => (
<AssignmentComponent
key={assignment.id}
assignment={assignment}
enrollment={enrollment}
unitId={unit.id}
/>
),
)}
</div>
))}
</div>
</div>

<h3>{unit.title}</h3>
<p>{unit.description}</p>
{unit.assignments?.map((assignment: Assignment) => (
<AssignmentComponent
key={assignment.id}
assignment={assignment}
enrollment={enrollment}
unitId={unit.id}
/>
))}
<div>
<div className="">
{/* Certificate */}
<h3 className="text-lg font-semibold text-gray-900 mb-4">
Complete this course to earn your verified
certificate
</h3>

<div className="w-full border border-gray-200 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 bg-white">
<div className="p-4">
<h4 className="text-lg font-semibold text-gray-900 mb-2">
Certificate of Completion
</h4>
<p className="text-sm text-gray-600">
Lorem ipsum dolor sit amet, consectetur
adipiscing elit. Sed pellentesque, purus sit
amet luctus venenatis, elit erat pretium
enim, nec ultricies lacus nunc nec nulla.
Nullam nec est ut sapien.
</p>
</div>
</div>
))}
</ul>
</div>
<Progress />
</div>
</div>
</div>
);
Expand Down
45 changes: 3 additions & 42 deletions frontend/src/app/courses/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import Link from "next/link";
import { type Prisma } from "@prisma/client";
import { computeCourseDuration } from "@/lib/utils";

type Course = Prisma.CourseGetPayload<{
include: { instructor: true };
}> & { duration: number };
import { type Course } from "@/types";
import CourseCard from "./CourseCard";

export const dynamic = "force-dynamic";

Expand All @@ -19,42 +15,7 @@ export default async function Courses() {
return (
<div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-5 gap-6">
{courses.map((course: Course) => (
<Link
key={course.id}
className="p-4 border border-gray-200 dark:border-gray-700 rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 dark:bg-gray-800 bg-white"
data-testid="course"
href={`/courses/${course.slug}`}
>
<div className="flex justify-between items-center mb-2">
<div className="flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400">
{course.difficulty}
</div>
<span className="text-sm text-gray-500 dark:text-gray-400">
{course.duration}
</span>
</div>
<div className="relative h-40 mb-2 overflow-hidden rounded-lg">
<img
src={course.thumbnail}
alt={course.title}
width={200}
height={200}
className="absolute inset-0 object-cover object-center w-full h-full"
/>
</div>
<h2 className="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">
{course.title}
</h2>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
{course.shortDescription}
</p>

<div className="flex justify-between items-center mt-4">
<span className="text-sm text-gray-500 dark:text-gray-400">
{course?.instructor?.name}
</span>
</div>
</Link>
<CourseCard key={course.id} course={course} />
))}
</div>
);
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ const geistMono = Geist_Mono({
});

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "BrightPath",
description:
"BrightPath is a custom platform for online courses covering niche tech topics not currently addressed elsewhere.",
};

export default async function RootLayout({
Expand Down
Loading

0 comments on commit 454d5f4

Please sign in to comment.