feat: course landing page

pull/8127/head
Arik Chakma 3 months ago
parent efb8478f56
commit e7272dd001
  1. 91
      src/components/CourseLanding/CourseChapterItem.tsx
  2. 60
      src/components/CourseLanding/CourseFloatingSidebar.tsx
  3. 18
      src/components/CourseLanding/CourseInfoCard.tsx
  4. 284
      src/components/CourseLanding/CourseLanding.tsx

@ -0,0 +1,91 @@
import { BookIcon, ChevronDownIcon, CodeXmlIcon } from 'lucide-react';
import { cn } from '../../lib/classname';
import type { LessonFrontmatter } from '../../lib/course';
import { useMemo, useState } from 'react';
type CourseChapterItemProps = {
title: string;
lessons: {
type: string;
title: string;
}[];
className?: string;
};
export function CourseChapterItem(props: CourseChapterItemProps) {
const { title, lessons, className } = props;
const [isOpen, setIsOpen] = useState(false);
const { excercises, textualLessons } = useMemo(() => {
const excercises: CourseChapterItemProps['lessons'] = [];
const textualLessons: CourseChapterItemProps['lessons'] = [];
lessons.forEach((lesson) => {
if (lesson.type === 'quiz' || lesson.type === 'challenge') {
excercises.push(lesson);
} else {
textualLessons.push(lesson);
}
});
return {
excercises,
textualLessons,
};
}, [lessons]);
return (
<div className={cn('border', className)}>
<div
role="button"
className="flex w-full items-center justify-between gap-1 p-2 pr-3"
onClick={() => setIsOpen(!isOpen)}
>
<span className="text-lg font-medium">{title}</span>
<div className="flex items-center gap-2">
{textualLessons.length > 0 && (
<span className="text-sm text-gray-500">
{textualLessons.length} Lesson
{textualLessons.length > 1 ? 's' : ''}
</span>
)}
{excercises.length > 0 && (
<span className="text-sm text-gray-500">
{excercises.length} Excerice
{excercises.length > 1 ? 's' : ''}
</span>
)}
<ChevronDownIcon
className={cn(
'size-3.5 stroke-[2.5] transition-transform',
isOpen ? 'rotate-180 transform' : '',
)}
/>
</div>
</div>
{isOpen && (
<div className="border-t">
{lessons.map((lesson, index) => {
return (
<div key={index} className="flex items-center gap-2 p-2">
<span className="text-gray-500">
{lesson.type === 'lesson' ? (
<BookIcon className="size-4 stroke-[2.5]" />
) : (
<CodeXmlIcon className="size-4 stroke-[2.5]" />
)}
</span>
<span>{lesson.title}</span>
</div>
);
})}
</div>
)}
</div>
);
}

@ -0,0 +1,60 @@
import { cn } from '../../lib/classname';
type CourseFloatingSidebarProps = {
isSticky: boolean;
};
export function CourseFloatingSidebar(props: CourseFloatingSidebarProps) {
const { isSticky } = props;
return (
<div
className={cn(
'sticky top-8 -translate-y-1/2 overflow-hidden rounded-lg border bg-white shadow-sm transition-transform',
isSticky && '-translate-y-0',
)}
>
<figure>
<img
src="https://images.unsplash.com/photo-1732200584655-3511db5c24e2?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw5fHx8ZW58MHx8fHx8"
alt="SQL 101"
className="aspect-video w-full object-cover"
/>
</figure>
<div className="p-2">
<button className="flex w-full items-center justify-between gap-1 rounded-lg bg-gradient-to-r from-purple-500 to-purple-700 p-2 px-3 text-slate-50">
<span>Enroll now</span>
<span>5$ / month</span>
</button>
</div>
<div className="border-b p-2 pb-4">
<h4 className="text-lg font-medium">Certificate of Completion</h4>
<p className="text-xs text-gray-500">
Certificate will be issued on completion
</p>
<figure className="mt-4">
<img
src="https://images.unsplash.com/photo-1732465286852-a0b95393a90d?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwxN3x8fGVufDB8fHx8fA%3D%3D"
alt="SQL 101"
className="aspect-video w-full rounded-lg object-cover"
/>
</figure>
</div>
<div className="p-2">
<h4 className="text-lg font-medium">What you get</h4>
<ul
role="list"
className="mt-2 flex list-disc flex-col gap-1 pl-4 text-sm text-gray-700 marker:text-gray-400"
>
<li>Full access to all the courses</li>
<li>Personalized access using AI</li>
<li>Certificate of Completion</li>
<li>Playground for live-coding</li>
<li>Challenges / Quizes</li>
</ul>
</div>
</div>
);
}

@ -0,0 +1,18 @@
import { cn } from '../../lib/classname';
type CourseInfoCardProps = {
title: string;
children: React.ReactNode;
className?: string;
};
export function CourseInfoCard(props: CourseInfoCardProps) {
const { title, children, className } = props;
return (
<div className={cn('rounded-lg border bg-white p-4 shadow-sm', className)}>
<h2 className="mb-4 text-xl font-medium">{title}</h2>
{children}
</div>
);
}

@ -1,4 +1,5 @@
import { import {
BookIcon,
CalendarIcon, CalendarIcon,
CodeXmlIcon, CodeXmlIcon,
LetterTextIcon, LetterTextIcon,
@ -9,6 +10,155 @@ import { Rating } from '../Rating/Rating';
import { CourseStatPill } from './CourseStatPill'; import { CourseStatPill } from './CourseStatPill';
import { useRef, useState, useEffect } from 'react'; import { useRef, useState, useEffect } from 'react';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { CourseInfoCard } from './CourseInfoCard';
import { ChevronDownIcon } from '../ReactIcons/ChevronDownIcon';
import { CourseChapterItem } from './CourseChapterItem';
import { CourseFloatingSidebar } from './CourseFloatingSidebar';
const DUMMY_COURSE_CONTENT = [
{
title: 'Introduction to SQL',
lessons: [
{
type: 'lesson',
title: 'What is SQL?',
},
{
type: 'lesson',
title: 'Why use SQL?',
},
{
type: 'lesson',
title: 'SQL Syntax',
},
{
type: 'quiz',
title: 'Quiz 1',
},
{
type: 'challenge',
title: 'Challenge 1',
},
],
},
{
title: 'Basic SQL Queries',
lessons: [
{
type: 'lesson',
title: 'SELECT Statement',
},
{
type: 'lesson',
title: 'WHERE Clause',
},
{
type: 'lesson',
title: 'ORDER BY Clause',
},
{
type: 'quiz',
title: 'Quiz 2',
},
{
type: 'challenge',
title: 'Challenge 2',
},
],
},
{
title: 'Advanced SQL Queries',
lessons: [
{
type: 'lesson',
title: 'JOIN Clause',
},
{
type: 'lesson',
title: 'GROUP BY Clause',
},
{
type: 'lesson',
title: 'HAVING Clause',
},
{
type: 'quiz',
title: 'Quiz 3',
},
{
type: 'challenge',
title: 'Challenge 3',
},
],
},
{
title: 'SQL Functions',
lessons: [
{
type: 'lesson',
title: 'COUNT() Function',
},
{
type: 'lesson',
title: 'SUM() Function',
},
{
type: 'lesson',
title: 'AVG() Function',
},
{
type: 'quiz',
title: 'Quiz 4',
},
{
type: 'challenge',
title: 'Challenge 4',
},
],
},
{
title: 'Database Design',
lessons: [
{
type: 'lesson',
title: 'Normalization',
},
{
type: 'lesson',
title: 'Denormalization',
},
{
type: 'lesson',
title: 'Indexes',
},
{
type: 'quiz',
title: 'Quiz 5',
},
{
type: 'challenge',
title: 'Challenge 5',
},
],
},
{
title: 'Optimizing Queries',
lessons: [
{
type: 'lesson',
title: 'Query Optimization',
},
{
type: 'lesson',
title: 'Indexing',
},
{
type: 'lesson',
title: 'Query Caching',
},
],
},
];
export function CourseLanding() { export function CourseLanding() {
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
@ -72,92 +222,60 @@ export function CourseLanding() {
</div> </div>
</div> </div>
<div className="container grid grid-cols-5 gap-6 py-8" ref={containerRef}> <div className="bg-gray-50">
<div className="col-start-1 col-end-4 space-y-4"> <div
<div className="rounded-md border p-4"> className="container grid grid-cols-5 gap-6 py-8"
<h2 className="text-xl font-medium">What you'll learn</h2> ref={containerRef}
<ul className="mt-4 grid grid-cols-2 gap-2"> >
<li>Understand SQL syntax</li> <div className="col-start-1 col-end-4 space-y-4">
<li>Write complex queries</li> <CourseInfoCard title="What you'll learn">
<li>Use SQL in real-world scenarios</li> <ul className="flex list-inside list-disc flex-col gap-1 text-sm text-gray-700 marker:text-gray-400">
<li>Optimize your queries</li> <li>Understand SQL syntax</li>
<li>Understand database design</li> <li>Write complex queries</li>
<li>Write complex queries</li> <li>Use SQL in real-world scenarios</li>
</ul> <li>Optimize your queries</li>
</div> <li>Understand database design</li>
<li>Write complex queries</li>
<div className="rounded-md border p-4"> </ul>
<h2 className="text-xl font-medium">About this Course</h2> </CourseInfoCard>
<CourseInfoCard title="About this Course">
<div className="prose mt-4"> <div className="prose-sm mt-4">
<p> <p>
SQL 101 is a beginner-friendly course that will teach you SQL 101 is a beginner-friendly course that will teach you
everything you need to know about SQL. It comes with an everything you need to know about SQL. It comes with an
interactive playground where you can practice your queries. interactive playground where you can practice your queries.
</p> </p>
<p>
The course is divided into multiple sections, each covering a
different aspect of SQL. You'll learn how to write complex
queries, use SQL in real-world scenarios, optimize your
queries, and understand database design.
</p>
</div>
</CourseInfoCard>
<p> <CourseInfoCard title="Course Content">
The course is divided into multiple sections, each covering a {DUMMY_COURSE_CONTENT.map((section, index) => {
different aspect of SQL. You'll learn how to write complex const { title, lessons } = section;
queries, use SQL in real-world scenarios, optimize your queries, const isFirst = index === 0;
and understand database design. const isLast = index === DUMMY_COURSE_CONTENT.length - 1;
</p>
</div>
<div className="h-[1000px]"></div> return (
<CourseChapterItem
key={title}
title={title}
lessons={lessons}
className={cn(
isFirst ? 'rounded-t-md' : '',
isLast ? 'rounded-b-md' : 'border-b-0',
)}
/>
);
})}
</CourseInfoCard>
</div> </div>
</div> <div className="col-start-4 col-end-6">
<CourseFloatingSidebar isSticky={isSticky} />
<div className="col-start-4 col-end-6">
<div
className={cn(
'sticky top-8 -translate-y-1/2 overflow-hidden rounded-lg border bg-white transition-transform',
isSticky && '-translate-y-0',
)}
>
<figure>
<img
src="https://images.unsplash.com/photo-1732200584655-3511db5c24e2?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHw5fHx8ZW58MHx8fHx8"
alt="SQL 101"
className="aspect-video w-full object-cover"
/>
</figure>
<div className="p-2">
<button className="flex w-full items-center justify-between gap-1 rounded-lg border p-2 px-3">
<span>Enroll now</span>
<span>5$ / month</span>
</button>
</div>
<div className="border-b p-2 pb-4">
<h4 className="text-lg font-medium">Certificate of Completion</h4>
<p className="text-xs text-gray-500">
Certificate will be issued on completion
</p>
<figure className="mt-4">
<img
src="https://images.unsplash.com/photo-1732465286852-a0b95393a90d?w=800&auto=format&fit=crop&q=60&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxmZWF0dXJlZC1waG90b3MtZmVlZHwxN3x8fGVufDB8fHx8fA%3D%3D"
alt="SQL 101"
className="aspect-video w-full rounded-lg object-cover"
/>
</figure>
</div>
<div className="p-2">
<h4 className="text-lg font-medium">What you get</h4>
<ul
role="list"
className="mt-2 list-disc pl-4 text-sm text-gray-700 marker:text-gray-400"
>
<li>Full access to all the courses</li>
<li>Personalized access using AI</li>
<li>Certificate of Completion</li>
<li>Playground for live-coding</li>
<li>Challenges / Quizes</li>
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save