From 3a20912f0f9666c253795dbef9a39c817bc6b679 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Tue, 8 Apr 2025 13:52:40 +0100
Subject: [PATCH 01/31] Add sidebar to ai-tutor

---
 src/components/AITutor/AITutorSidebar.tsx     | 66 +++++++++++++++++++
 .../GenerateCourse/FineTuneCourse.tsx         |  3 +-
 src/pages/ai/index.astro                      | 21 +++---
 3 files changed, 80 insertions(+), 10 deletions(-)
 create mode 100644 src/components/AITutor/AITutorSidebar.tsx

diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
new file mode 100644
index 000000000..2ef0ca0c8
--- /dev/null
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -0,0 +1,66 @@
+import { ChevronLeft, PlusCircle, BookOpen, Compass } from 'lucide-react';
+
+type AITutorSidebarProps = {
+  activeTab: 'new' | 'courses' | 'explore';
+};
+
+const sidebarItems = [
+  {
+    key: 'new',
+    label: 'New Course',
+    href: '/ai/new',
+    icon: PlusCircle,
+  },
+  {
+    key: 'courses',
+    label: 'My Courses',
+    href: '/ai/courses',
+    icon: BookOpen,
+  },
+  {
+    key: 'explore',
+    label: 'Explore',
+    href: '/ai/explore',
+    icon: Compass,
+  },
+];
+
+export function AITutorSidebar(props: AITutorSidebarProps) {
+  const { activeTab } = props;
+
+  return (
+    <div className="flex w-[240px] flex-col border-r border-gray-200 bg-gradient-to-b from-white to-gray-50">
+      <a
+        href="https://roadmap.sh"
+        className="flex w-full items-center justify-start gap-1.5 border-b border-gray-200 px-5 py-2 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-black"
+      >
+        <ChevronLeft className="size-4" />
+        Back to <span className="font-semibold text-black">roadmap.sh</span>
+      </a>
+
+      <div className="px-6 pt-6 pb-2">
+        <h2 className="text-lg font-semibold text-gray-900">Learn with AI</h2>
+        <p className="mt-1 text-sm text-gray-500">
+          Your personalized learning companion for any topic
+        </p>
+      </div>
+
+      <div className="flex-1 px-3 py-3">
+        <nav className="space-y-1">
+          {sidebarItems.map((item) => (
+            <a
+              key={item.key}
+              href={item.href}
+              className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-gray-700 transition-all hover:bg-gray-100 hover:text-black ${
+                activeTab === item.key ? 'bg-gray-100 text-black' : ''
+              }`}
+            >
+              <item.icon className="size-4" />
+              {item.label}
+            </a>
+          ))}
+        </nav>
+      </div>
+    </div>
+  );
+}
diff --git a/src/components/GenerateCourse/FineTuneCourse.tsx b/src/components/GenerateCourse/FineTuneCourse.tsx
index 44d967b0f..e1641ca59 100644
--- a/src/components/GenerateCourse/FineTuneCourse.tsx
+++ b/src/components/GenerateCourse/FineTuneCourse.tsx
@@ -1,4 +1,3 @@
-import { useState } from 'react';
 import { cn } from '../../lib/classname';
 
 type QuestionProps = {
@@ -70,7 +69,7 @@ export function FineTuneCourse(props: FineTuneCourseProps) {
           }}
         />
         Tell us more to tailor the course (optional){' '}
-        <span className="ml-auto rounded-md bg-gray-400 px-2 py-0.5 text-xs text-white">
+        <span className="ml-auto rounded-md bg-gray-400 px-2 py-0.5 text-xs text-white hidden sm:block">
           recommended
         </span>
       </label>
diff --git a/src/pages/ai/index.astro b/src/pages/ai/index.astro
index 3a67adf52..c0e6a1f6f 100644
--- a/src/pages/ai/index.astro
+++ b/src/pages/ai/index.astro
@@ -1,18 +1,23 @@
 ---
-import { AICourse } from '../../components/GenerateCourse/AICourse';
-import BaseLayout from '../../layouts/BaseLayout.astro';
+import { ChevronLeft, PlusCircle, BookOpen, Compass } from 'lucide-react';
 import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification';
-
+import { AICourse } from '../../components/GenerateCourse/AICourse';
+import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
+import { AITutorSidebar } from '../../components/AITutor/AITutorSidebar';
 const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
 ---
 
-<BaseLayout
+<SkeletonLayout
   title='Roadmap AI'
   noIndex={true}
   ogImageUrl={ogImage}
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
-  <div slot='course-announcement'></div>
-  <AICourse client:load />
-  <CheckSubscriptionVerification client:load />
-</BaseLayout>
+  <div class='flex flex-grow flex-row'>
+    <AITutorSidebar client:load activeTab='new' />
+    <div class='flex flex-grow flex-col'>
+      <AICourse client:load />
+      <CheckSubscriptionVerification client:load />
+    </div>
+  </div>
+</SkeletonLayout>

From da14f050796cfabf324a00c110dee1a56d38c0e4 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 01:39:51 +0600
Subject: [PATCH 02/31] wip

---
 .../AITutor/AIExploreCourseListing.tsx        | 96 ++++++++++++++++++
 .../AITutor/AIFeaturedCoursesListing.tsx      | 98 +++++++++++++++++++
 src/components/AITutor/AITutorLayout.tsx      | 17 ++++
 src/components/AITutor/AITutorSidebar.tsx     | 20 +++-
 src/components/GenerateCourse/AICourse.tsx    |  8 +-
 .../GenerateCourse/AICourseCard.tsx           | 10 +-
 src/pages/ai/courses.astro                    | 21 ++++
 src/pages/ai/explore.astro                    | 21 ++++
 src/pages/ai/index.astro                      | 13 +--
 src/pages/ai/stuff-picks.astro                | 21 ++++
 src/queries/ai-course.ts                      | 48 ++++++++-
 11 files changed, 351 insertions(+), 22 deletions(-)
 create mode 100644 src/components/AITutor/AIExploreCourseListing.tsx
 create mode 100644 src/components/AITutor/AIFeaturedCoursesListing.tsx
 create mode 100644 src/components/AITutor/AITutorLayout.tsx
 create mode 100644 src/pages/ai/courses.astro
 create mode 100644 src/pages/ai/explore.astro
 create mode 100644 src/pages/ai/stuff-picks.astro

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
new file mode 100644
index 000000000..65b57f1b2
--- /dev/null
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -0,0 +1,96 @@
+import { useListExploreAiCourses } from '../../queries/ai-course';
+import { useEffect, useState } from 'react';
+import { AlertCircle, Loader2 } from 'lucide-react';
+import { AICourseCard } from '../GenerateCourse/AICourseCard';
+
+type AIExploreCourseListingProps = {};
+
+export function AIExploreCourseListing(props: AIExploreCourseListingProps) {
+  const [isInitialLoading, setIsInitialLoading] = useState(true);
+
+  const {
+    data,
+    error,
+    fetchNextPage,
+    hasNextPage,
+    isFetching,
+    isFetchingNextPage,
+    status,
+    isLoading: isExploreAiCoursesLoading,
+  } = useListExploreAiCourses();
+
+  useEffect(() => {
+    setIsInitialLoading(false);
+  }, [data]);
+
+  const courses = data?.pages.flatMap((page) => page.data) ?? [];
+
+  return (
+    <>
+      <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
+        <div className="flex items-center gap-2">
+          <h2 className="text-lg font-semibold">Explore Courses</h2>
+        </div>
+      </div>
+
+      {(isExploreAiCoursesLoading || isInitialLoading) && (
+        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
+          <Loader2
+            className="size-4 animate-spin text-gray-400"
+            strokeWidth={2.5}
+          />
+          <p className="text-sm font-medium text-gray-600">Loading...</p>
+        </div>
+      )}
+
+      {error && !isExploreAiCoursesLoading && !isInitialLoading && (
+        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
+          <AlertCircle className="size-4 text-red-500" />
+          <p className="text-sm font-medium text-red-600">
+            {error?.message ?? 'Error loading courses.'}
+          </p>
+        </div>
+      )}
+
+      {!isExploreAiCoursesLoading &&
+        courses &&
+        courses.length > 0 &&
+        !error && (
+          <div className="flex flex-col gap-2">
+            {courses.map((course) => (
+              <AICourseCard
+                key={course._id}
+                course={course}
+                showActions={false}
+                showProgress={false}
+              />
+            ))}
+          </div>
+        )}
+
+      {hasNextPage && !isFetchingNextPage && !error && (
+        <div className="mt-4 flex items-center justify-center">
+          <button
+            onClick={() => fetchNextPage()}
+            disabled={isFetchingNextPage}
+            className="rounded-md border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-100 disabled:opacity-50"
+          >
+            Load more
+          </button>
+        </div>
+      )}
+
+      {isFetchingNextPage && !error && (
+        <div className="mt-4 flex items-center justify-center gap-2">
+          <Loader2
+            className="size-4 animate-spin text-gray-400"
+            strokeWidth={2.5}
+          />
+          <p className="text-sm font-medium text-gray-600">
+            Loading more courses...
+          </p>
+        </div>
+      )}
+    </>
+  );
+}
diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
new file mode 100644
index 000000000..b65cd022f
--- /dev/null
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -0,0 +1,98 @@
+import { useQuery } from '@tanstack/react-query';
+import {
+  listUserAiCoursesOptions,
+  type ListUserAiCoursesQuery,
+} from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { useEffect, useState } from 'react';
+import { Loader2 } from 'lucide-react';
+import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
+import { AICourseCard } from '../GenerateCourse/AICourseCard';
+
+type AIFeaturedCoursesListingProps = {};
+
+export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
+  const [isInitialLoading, setIsInitialLoading] = useState(true);
+
+  const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
+    perPage: '10',
+    currPage: '1',
+    query: '',
+  });
+
+  const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
+    listUserAiCoursesOptions(pageState),
+    queryClient,
+  );
+
+  useEffect(() => {
+    setIsInitialLoading(false);
+  }, [userAiCourses]);
+
+  const courses = userAiCourses?.data ?? [];
+
+  useEffect(() => {
+    const queryParams = getUrlParams();
+
+    setPageState({
+      ...pageState,
+      currPage: queryParams?.p || '1',
+      query: queryParams?.q || '',
+    });
+  }, []);
+
+  useEffect(() => {
+    if (pageState?.currPage !== '1' || pageState?.query !== '') {
+      setUrlParams({
+        p: pageState?.currPage || '1',
+        q: pageState?.query || '',
+      });
+    } else {
+      deleteUrlParam('p');
+      deleteUrlParam('q');
+    }
+  }, [pageState]);
+
+  return (
+    <>
+      <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
+        <div className="flex items-center gap-2">
+          <h2 className="text-lg font-semibold">Stuff Picks</h2>
+        </div>
+      </div>
+
+      {(isUserAiCoursesLoading || isInitialLoading) && (
+        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
+          <Loader2
+            className="size-4 animate-spin text-gray-400"
+            strokeWidth={2.5}
+          />
+          <p className="text-sm font-medium text-gray-600">Loading...</p>
+        </div>
+      )}
+
+      {!isUserAiCoursesLoading && courses && courses.length > 0 && (
+        <div className="flex flex-col gap-2">
+          {courses.map((course) => (
+            <AICourseCard
+              key={course._id}
+              course={course}
+              showActions={false}
+              showProgress={false}
+            />
+          ))}
+        </div>
+      )}
+
+      {!isUserAiCoursesLoading &&
+        (userAiCourses?.data?.length || 0 > 0) &&
+        courses.length === 0 && (
+          <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
+            <p className="text-sm text-gray-600">
+              No courses match your search.
+            </p>
+          </div>
+        )}
+    </>
+  );
+}
diff --git a/src/components/AITutor/AITutorLayout.tsx b/src/components/AITutor/AITutorLayout.tsx
new file mode 100644
index 000000000..18b23649f
--- /dev/null
+++ b/src/components/AITutor/AITutorLayout.tsx
@@ -0,0 +1,17 @@
+import { AITutorSidebar, type AITutorTab } from './AITutorSidebar';
+
+type AITutorLayoutProps = {
+  children: React.ReactNode;
+  activeTab: AITutorTab;
+};
+
+export function AITutorLayout(props: AITutorLayoutProps) {
+  const { children, activeTab } = props;
+
+  return (
+    <div className="flex flex-grow flex-row">
+      <AITutorSidebar activeTab={activeTab} />
+      <div className="flex flex-grow flex-col">{children}</div>
+    </div>
+  );
+}
diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 2ef0ca0c8..2d1f15fe0 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,14 +1,20 @@
-import { ChevronLeft, PlusCircle, BookOpen, Compass } from 'lucide-react';
+import {
+  ChevronLeft,
+  PlusCircle,
+  BookOpen,
+  Compass,
+  CircleDotIcon,
+} from 'lucide-react';
 
 type AITutorSidebarProps = {
-  activeTab: 'new' | 'courses' | 'explore';
+  activeTab: AITutorTab;
 };
 
 const sidebarItems = [
   {
     key: 'new',
     label: 'New Course',
-    href: '/ai/new',
+    href: '/ai',
     icon: PlusCircle,
   },
   {
@@ -17,6 +23,12 @@ const sidebarItems = [
     href: '/ai/courses',
     icon: BookOpen,
   },
+  {
+    key: 'stuff-picks',
+    label: 'Stuff Picks',
+    href: '/ai/stuff-picks',
+    icon: CircleDotIcon,
+  },
   {
     key: 'explore',
     label: 'Explore',
@@ -25,6 +37,8 @@ const sidebarItems = [
   },
 ];
 
+export type AITutorTab = (typeof sidebarItems)[number]['key'];
+
 export function AITutorSidebar(props: AITutorSidebarProps) {
   const { activeTab } = props;
 
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index afbb3fcbf..5708a505c 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -97,7 +97,7 @@ export function AICourse(props: AICourseProps) {
                 Course Topic
               </label>
               <div className="relative">
-                <div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
+                <div className="absolute top-1/2 left-3 -translate-y-1/2 text-gray-400">
                   <SearchIcon size={18} />
                 </div>
                 <input
@@ -107,7 +107,7 @@ export function AICourse(props: AICourseProps) {
                   onChange={(e) => setKeyword(e.target.value)}
                   onKeyDown={handleKeyDown}
                   placeholder="e.g., Algebra, JavaScript, Photography"
-                  className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:outline-hidden focus:ring-1 focus:ring-gray-500 max-sm:placeholder:text-base"
+                  className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:ring-1 focus:ring-gray-500 focus:outline-hidden max-sm:placeholder:text-base"
                   maxLength={50}
                 />
               </div>
@@ -162,10 +162,6 @@ export function AICourse(props: AICourseProps) {
             </button>
           </form>
         </div>
-
-        <div className="mt-8 min-h-[200px]">
-          <UserCoursesList />
-        </div>
       </div>
     </section>
   );
diff --git a/src/components/GenerateCourse/AICourseCard.tsx b/src/components/GenerateCourse/AICourseCard.tsx
index 5c524885a..12e4bf09b 100644
--- a/src/components/GenerateCourse/AICourseCard.tsx
+++ b/src/components/GenerateCourse/AICourseCard.tsx
@@ -5,10 +5,12 @@ import { AICourseActions } from './AICourseActions';
 
 type AICourseCardProps = {
   course: AICourseWithLessonCount;
+  showActions?: boolean;
+  showProgress?: boolean;
 };
 
 export function AICourseCard(props: AICourseCardProps) {
-  const { course } = props;
+  const { course, showActions = true, showProgress = true } = props;
 
   // Format date if available
   const formattedDate = course.createdAt
@@ -56,7 +58,7 @@ export function AICourseCard(props: AICourseCardProps) {
             <span>{totalTopics} lessons</span>
           </div>
 
-          {totalTopics > 0 && (
+          {showProgress && totalTopics > 0 && (
             <div className="flex items-center">
               <div className="mr-2 h-1.5 w-16 overflow-hidden rounded-full bg-gray-200">
                 <div
@@ -72,8 +74,8 @@ export function AICourseCard(props: AICourseCardProps) {
         </div>
       </a>
 
-      {course.slug && (
-        <div className="absolute right-2 top-2">
+      {showActions && course.slug && (
+        <div className="absolute top-2 right-2">
           <AICourseActions courseSlug={course.slug} />
         </div>
       )}
diff --git a/src/pages/ai/courses.astro b/src/pages/ai/courses.astro
new file mode 100644
index 000000000..e54254731
--- /dev/null
+++ b/src/pages/ai/courses.astro
@@ -0,0 +1,21 @@
+---
+import { UserCoursesList } from '../../components/GenerateCourse/UserCoursesList';
+import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
+import { AITutorLayout } from '../../components/AITutor/AITutorLayout';
+const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
+---
+
+<SkeletonLayout
+  title='Roadmap AI'
+  noIndex={true}
+  ogImageUrl={ogImage}
+  description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
+>
+  <AITutorLayout activeTab='courses' client:load>
+    <section class='flex grow flex-col bg-gray-100'>
+      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
+        <UserCoursesList client:load />
+      </div>
+    </section>
+  </AITutorLayout>
+</SkeletonLayout>
diff --git a/src/pages/ai/explore.astro b/src/pages/ai/explore.astro
new file mode 100644
index 000000000..60f0e352a
--- /dev/null
+++ b/src/pages/ai/explore.astro
@@ -0,0 +1,21 @@
+---
+import { AIExploreCourseListing } from '../../components/AITutor/AIExploreCourseListing';
+import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
+import { AITutorLayout } from '../../components/AITutor/AITutorLayout';
+const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
+---
+
+<SkeletonLayout
+  title='Roadmap AI'
+  noIndex={true}
+  ogImageUrl={ogImage}
+  description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
+>
+  <AITutorLayout activeTab='explore' client:load>
+    <section class='flex grow flex-col bg-gray-100'>
+      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
+        <AIExploreCourseListing client:load />
+      </div>
+    </section>
+  </AITutorLayout>
+</SkeletonLayout>
diff --git a/src/pages/ai/index.astro b/src/pages/ai/index.astro
index c0e6a1f6f..16db0eceb 100644
--- a/src/pages/ai/index.astro
+++ b/src/pages/ai/index.astro
@@ -3,7 +3,7 @@ import { ChevronLeft, PlusCircle, BookOpen, Compass } from 'lucide-react';
 import { CheckSubscriptionVerification } from '../../components/Billing/CheckSubscriptionVerification';
 import { AICourse } from '../../components/GenerateCourse/AICourse';
 import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
-import { AITutorSidebar } from '../../components/AITutor/AITutorSidebar';
+import { AITutorLayout } from '../../components/AITutor/AITutorLayout';
 const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
 ---
 
@@ -13,11 +13,8 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   ogImageUrl={ogImage}
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
-  <div class='flex flex-grow flex-row'>
-    <AITutorSidebar client:load activeTab='new' />
-    <div class='flex flex-grow flex-col'>
-      <AICourse client:load />
-      <CheckSubscriptionVerification client:load />
-    </div>
-  </div>
+  <AITutorLayout activeTab='new' client:load>
+    <AICourse client:load />
+    <CheckSubscriptionVerification client:load />
+  </AITutorLayout>
 </SkeletonLayout>
diff --git a/src/pages/ai/stuff-picks.astro b/src/pages/ai/stuff-picks.astro
new file mode 100644
index 000000000..ad7ad5f74
--- /dev/null
+++ b/src/pages/ai/stuff-picks.astro
@@ -0,0 +1,21 @@
+---
+import { AIFeaturedCoursesListing } from '../../components/AITutor/AIFeaturedCoursesListing';
+import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
+import { AITutorLayout } from '../../components/AITutor/AITutorLayout';
+const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
+---
+
+<SkeletonLayout
+  title='Roadmap AI'
+  noIndex={true}
+  ogImageUrl={ogImage}
+  description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
+>
+  <AITutorLayout activeTab='stuff-picks' client:load>
+    <section class='flex grow flex-col bg-gray-100'>
+      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
+        <AIFeaturedCoursesListing client:load />
+      </div>
+    </section>
+  </AITutorLayout>
+</SkeletonLayout>
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index 0a955bb12..0ab1a601c 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -1,6 +1,7 @@
 import { httpGet } from '../lib/query-http';
 import { isLoggedIn } from '../lib/jwt';
-import { queryOptions } from '@tanstack/react-query';
+import { queryOptions, useInfiniteQuery } from '@tanstack/react-query';
+import { queryClient } from '../stores/query-client';
 
 export interface AICourseProgressDocument {
   _id: string;
@@ -99,3 +100,48 @@ export function listUserAiCoursesOptions(
     enabled: !!isLoggedIn(),
   };
 }
+
+type ListExploreAiCoursesParams = {};
+
+type ListExploreAiCoursesQuery = {
+  perPage?: string;
+  currPage?: string;
+};
+
+type ListExploreAiCoursesResponse = {
+  data: AICourseWithLessonCount[];
+  currPage: number;
+  perPage: number;
+};
+
+export function useListExploreAiCourses() {
+  return useInfiniteQuery(
+    {
+      queryKey: ['explore-ai-courses'],
+      queryFn: ({ pageParam = 1 }) => {
+        return httpGet<ListExploreAiCoursesResponse>(
+          `/v1-list-explore-ai-courses`,
+          {
+            perPage: '20',
+            currPage: String(pageParam),
+          },
+        );
+      },
+      getNextPageParam: (lastPage, pages, lastPageParam) => {
+        if (lastPage?.data?.length === 0) {
+          return undefined;
+        }
+
+        return lastPageParam + 1;
+      },
+      getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
+        if (firstPageParam <= 1) {
+          return undefined;
+        }
+        return firstPageParam - 1;
+      },
+      initialPageParam: 1,
+    },
+    queryClient,
+  );
+}

From 69ed5d79de5bfe277ab64125aefcc0c3a47e3dbc Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 01:47:53 +0600
Subject: [PATCH 03/31] wip

---
 .../AITutor/AIFeaturedCoursesListing.tsx      | 39 +++++++++++--------
 src/queries/ai-course.ts                      | 32 +++++++++++++++
 2 files changed, 55 insertions(+), 16 deletions(-)

diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
index b65cd022f..a5df649ed 100644
--- a/src/components/AITutor/AIFeaturedCoursesListing.tsx
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -1,5 +1,6 @@
 import { useQuery } from '@tanstack/react-query';
 import {
+  listFeaturedAiCoursesOptions,
   listUserAiCoursesOptions,
   type ListUserAiCoursesQuery,
 } from '../../queries/ai-course';
@@ -8,6 +9,7 @@ import { useEffect, useState } from 'react';
 import { Loader2 } from 'lucide-react';
 import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
+import { Pagination } from '../Pagination/Pagination';
 
 type AIFeaturedCoursesListingProps = {};
 
@@ -15,21 +17,18 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
 
   const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
-    perPage: '10',
+    perPage: '20',
     currPage: '1',
-    query: '',
   });
 
-  const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
-    listUserAiCoursesOptions(pageState),
-    queryClient,
-  );
+  const { data: featuredAiCourses, isFetching: isFeaturedAiCoursesLoading } =
+    useQuery(listFeaturedAiCoursesOptions(pageState), queryClient);
 
   useEffect(() => {
     setIsInitialLoading(false);
-  }, [userAiCourses]);
+  }, [featuredAiCourses]);
 
-  const courses = userAiCourses?.data ?? [];
+  const courses = featuredAiCourses?.data ?? [];
 
   useEffect(() => {
     const queryParams = getUrlParams();
@@ -37,19 +36,16 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
     setPageState({
       ...pageState,
       currPage: queryParams?.p || '1',
-      query: queryParams?.q || '',
     });
   }, []);
 
   useEffect(() => {
-    if (pageState?.currPage !== '1' || pageState?.query !== '') {
+    if (pageState?.currPage !== '1') {
       setUrlParams({
         p: pageState?.currPage || '1',
-        q: pageState?.query || '',
       });
     } else {
       deleteUrlParam('p');
-      deleteUrlParam('q');
     }
   }, [pageState]);
 
@@ -61,7 +57,7 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
         </div>
       </div>
 
-      {(isUserAiCoursesLoading || isInitialLoading) && (
+      {(isFeaturedAiCoursesLoading || isInitialLoading) && (
         <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
           <Loader2
             className="size-4 animate-spin text-gray-400"
@@ -71,7 +67,7 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
         </div>
       )}
 
-      {!isUserAiCoursesLoading && courses && courses.length > 0 && (
+      {!isFeaturedAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
           {courses.map((course) => (
             <AICourseCard
@@ -81,11 +77,22 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
               showProgress={false}
             />
           ))}
+
+          <Pagination
+            totalCount={featuredAiCourses?.totalCount || 0}
+            totalPages={featuredAiCourses?.totalPages || 0}
+            currPage={Number(featuredAiCourses?.currPage || 1)}
+            perPage={Number(featuredAiCourses?.perPage || 10)}
+            onPageChange={(page) => {
+              setPageState({ ...pageState, currPage: String(page) });
+            }}
+            className="rounded-lg border border-gray-200 bg-white p-4"
+          />
         </div>
       )}
 
-      {!isUserAiCoursesLoading &&
-        (userAiCourses?.data?.length || 0 > 0) &&
+      {!isFeaturedAiCoursesLoading &&
+        (featuredAiCourses?.data?.length || 0 > 0) &&
         courses.length === 0 && (
           <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
             <p className="text-sm text-gray-600">
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index 0ab1a601c..4f90e2081 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -101,6 +101,38 @@ export function listUserAiCoursesOptions(
   };
 }
 
+type ListFeaturedAiCoursesParams = {};
+
+type ListFeaturedAiCoursesQuery = {
+  perPage?: string;
+  currPage?: string;
+};
+
+type ListFeaturedAiCoursesResponse = {
+  data: AICourseWithLessonCount[];
+  totalCount: number;
+  totalPages: number;
+  currPage: number;
+  perPage: number;
+};
+
+export function listFeaturedAiCoursesOptions(
+  params: ListFeaturedAiCoursesQuery = {
+    perPage: '10',
+    currPage: '1',
+  },
+) {
+  return {
+    queryKey: ['featured-ai-courses', params],
+    queryFn: () => {
+      return httpGet<ListFeaturedAiCoursesResponse>(
+        `/v1-list-featured-ai-courses`,
+        params,
+      );
+    },
+  };
+}
+
 type ListExploreAiCoursesParams = {};
 
 type ListExploreAiCoursesQuery = {

From d1208047a5b71a80237e9235438b24687275ed72 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 12:44:25 +0100
Subject: [PATCH 04/31] Fix mistakes and refacctor

---
 src/components/AITutor/AIExploreCourseListing.tsx     | 2 +-
 src/components/AITutor/AITutorSidebar.tsx             | 6 +++---
 src/components/GenerateCourse/AICourseCard.tsx        | 2 +-
 src/pages/ai/explore.astro                            | 2 +-
 src/pages/ai/{stuff-picks.astro => staff-picks.astro} | 0
 5 files changed, 6 insertions(+), 6 deletions(-)
 rename src/pages/ai/{stuff-picks.astro => staff-picks.astro} (100%)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index 65b57f1b2..4f132e871 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -56,7 +56,7 @@ export function AIExploreCourseListing(props: AIExploreCourseListingProps) {
         courses &&
         courses.length > 0 &&
         !error && (
-          <div className="flex flex-col gap-2">
+          <div className="grid grid-cols-2 gap-2">
             {courses.map((course) => (
               <AICourseCard
                 key={course._id}
diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 2d1f15fe0..7ebc5006e 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -24,9 +24,9 @@ const sidebarItems = [
     icon: BookOpen,
   },
   {
-    key: 'stuff-picks',
-    label: 'Stuff Picks',
-    href: '/ai/stuff-picks',
+    key: 'staff-picks',
+    label: 'Staff Picks',
+    href: '/ai/staff-picks',
     icon: CircleDotIcon,
   },
   {
diff --git a/src/components/GenerateCourse/AICourseCard.tsx b/src/components/GenerateCourse/AICourseCard.tsx
index 12e4bf09b..0d758978b 100644
--- a/src/components/GenerateCourse/AICourseCard.tsx
+++ b/src/components/GenerateCourse/AICourseCard.tsx
@@ -38,7 +38,7 @@ export function AICourseCard(props: AICourseCardProps) {
     <div className="relative">
       <a
         href={`/ai/${course.slug}`}
-        className="hover:border-gray-3 00 group relative flex w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:bg-gray-50"
+        className="hover:border-gray-3 00 group relative flex w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:bg-gray-50 min-h-full "
       >
         <div className="flex items-center justify-between">
           <span
diff --git a/src/pages/ai/explore.astro b/src/pages/ai/explore.astro
index 60f0e352a..b743e98af 100644
--- a/src/pages/ai/explore.astro
+++ b/src/pages/ai/explore.astro
@@ -13,7 +13,7 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
 >
   <AITutorLayout activeTab='explore' client:load>
     <section class='flex grow flex-col bg-gray-100'>
-      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
+      <div class='mx-auto w-full flex max-w-4xl flex-col py-10 max-sm:py-4'>
         <AIExploreCourseListing client:load />
       </div>
     </section>
diff --git a/src/pages/ai/stuff-picks.astro b/src/pages/ai/staff-picks.astro
similarity index 100%
rename from src/pages/ai/stuff-picks.astro
rename to src/pages/ai/staff-picks.astro

From d0f8fc4e6af06e5b65967bfd3f65cf1e6e25a323 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 14:18:02 +0100
Subject: [PATCH 05/31] AI landing page changes

---
 src/components/AITutor/AILoadingState.tsx     |  27 +++
 src/components/AITutor/AITutorLayout.tsx      |   4 +-
 src/components/AITutor/AITutorTallMessage.tsx |  31 ++++
 src/components/GenerateCourse/AICourse.tsx    | 175 +++++++++---------
 .../GenerateCourse/AICourseCard.tsx           |  12 +-
 .../GenerateCourse/UserCoursesList.tsx        |  86 +++++----
 src/pages/ai/courses.astro                    |   6 +-
 7 files changed, 196 insertions(+), 145 deletions(-)
 create mode 100644 src/components/AITutor/AILoadingState.tsx
 create mode 100644 src/components/AITutor/AITutorTallMessage.tsx

diff --git a/src/components/AITutor/AILoadingState.tsx b/src/components/AITutor/AILoadingState.tsx
new file mode 100644
index 000000000..66ed9de4e
--- /dev/null
+++ b/src/components/AITutor/AILoadingState.tsx
@@ -0,0 +1,27 @@
+import { Loader2 } from 'lucide-react';
+
+type AILoadingStateProps = {
+  title: string;
+  subtitle?: string;
+};
+
+export function AILoadingState(props: AILoadingStateProps) {
+  const { title, subtitle } = props;
+
+  return (
+    <div className="flex min-h-full w-full flex-col items-center justify-center gap-4 rounded-lg border border-gray-200 bg-white p-8">
+      <div className="relative">
+        <Loader2 className="size-12 animate-spin text-gray-300" />
+        <div className="absolute inset-0 flex items-center justify-center">
+          <div className="size-4 rounded-full bg-white"></div>
+        </div>
+      </div>
+      <div className="text-center">
+        <p className="text-lg font-medium text-gray-900">{title}</p>
+        {subtitle && (
+          <p className="mt-1 text-sm text-gray-500">{subtitle}</p>
+        )}
+      </div>
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/src/components/AITutor/AITutorLayout.tsx b/src/components/AITutor/AITutorLayout.tsx
index 18b23649f..5cfe7f3ce 100644
--- a/src/components/AITutor/AITutorLayout.tsx
+++ b/src/components/AITutor/AITutorLayout.tsx
@@ -11,7 +11,9 @@ export function AITutorLayout(props: AITutorLayoutProps) {
   return (
     <div className="flex flex-grow flex-row">
       <AITutorSidebar activeTab={activeTab} />
-      <div className="flex flex-grow flex-col">{children}</div>
+      <div className="flex flex-grow flex-col bg-gray-100 px-4 py-4">
+        {children}
+      </div>
     </div>
   );
 }
diff --git a/src/components/AITutor/AITutorTallMessage.tsx b/src/components/AITutor/AITutorTallMessage.tsx
new file mode 100644
index 000000000..a0000a3ff
--- /dev/null
+++ b/src/components/AITutor/AITutorTallMessage.tsx
@@ -0,0 +1,31 @@
+import { type LucideIcon } from 'lucide-react';
+
+type AITutorTallMessageProps = {
+  title: string;
+  subtitle?: string;
+  icon: LucideIcon;
+  buttonText?: string;
+  onButtonClick?: () => void;
+};
+
+export function AITutorTallMessage(props: AITutorTallMessageProps) {
+  const { title, subtitle, icon: Icon, buttonText, onButtonClick } = props;
+
+  return (
+    <div className="flex min-h-full flex-grow flex-col items-center justify-center rounded-lg">
+      <Icon className="size-12 text-gray-300" />
+      <div className="my-4 text-center">
+        <h2 className="mb-2 text-xl font-semibold">{title}</h2>
+        {subtitle && <p className="text-base text-gray-600">{subtitle}</p>}
+      </div>
+      {buttonText && onButtonClick && (
+        <button
+          onClick={onButtonClick}
+          className="rounded-lg bg-black px-4 py-2 text-sm text-white hover:opacity-80"
+        >
+          {buttonText}
+        </button>
+      )}
+    </div>
+  );
+}
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index 5708a505c..428fb21fe 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
 import { cn } from '../../lib/classname';
 import { isLoggedIn } from '../../lib/jwt';
 import { showLoginPopup } from '../../lib/popup';
-import { UserCoursesList } from './UserCoursesList';
 import { FineTuneCourse } from './FineTuneCourse';
 import {
   clearFineTuneData,
@@ -72,97 +71,95 @@ export function AICourse(props: AICourseProps) {
   }
 
   return (
-    <section className="flex grow flex-col bg-gray-100">
-      <div className="container mx-auto flex max-w-3xl flex-col py-24 max-sm:py-4">
-        <h1 className="mb-2.5 text-center text-4xl font-bold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
-          Learn anything with AI
-        </h1>
-        <p className="mb-6 text-center text-lg text-gray-600 max-sm:hidden max-sm:text-left max-sm:text-sm">
-          Enter a topic below to generate a personalized course for it
-        </p>
-
-        <div className="rounded-lg border border-gray-200 bg-white p-6 max-sm:p-4">
-          <form
-            className="flex flex-col gap-5"
-            onSubmit={(e) => {
-              e.preventDefault();
-              onSubmit();
-            }}
-          >
-            <div className="flex flex-col">
-              <label
-                htmlFor="keyword"
-                className="mb-2.5 text-sm font-medium text-gray-700"
-              >
-                Course Topic
-              </label>
-              <div className="relative">
-                <div className="absolute top-1/2 left-3 -translate-y-1/2 text-gray-400">
-                  <SearchIcon size={18} />
-                </div>
-                <input
-                  id="keyword"
-                  type="text"
-                  value={keyword}
-                  onChange={(e) => setKeyword(e.target.value)}
-                  onKeyDown={handleKeyDown}
-                  placeholder="e.g., Algebra, JavaScript, Photography"
-                  className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:ring-1 focus:ring-gray-500 focus:outline-hidden max-sm:placeholder:text-base"
-                  maxLength={50}
-                />
+    <div className="flex w-full max-w-3xl mx-auto flex-grow flex-col justify-center">
+      <h1 className="mb-2.5 text-center text-4xl font-bold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
+        What can I help you learn?
+      </h1>
+      <p className="mb-6 text-center text-lg text-gray-600 max-sm:hidden max-sm:text-left max-sm:text-sm">
+        Enter a topic below to generate a personalized course for it
+      </p>
+
+      <div className="rounded-lg border border-gray-200 bg-white p-6 max-sm:p-4">
+        <form
+          className="flex flex-col gap-5"
+          onSubmit={(e) => {
+            e.preventDefault();
+            onSubmit();
+          }}
+        >
+          <div className="flex flex-col">
+            <label
+              htmlFor="keyword"
+              className="mb-2.5 text-sm font-medium text-gray-700"
+            >
+              Course Topic
+            </label>
+            <div className="relative">
+              <div className="absolute top-1/2 left-3 -translate-y-1/2 text-gray-400">
+                <SearchIcon size={18} />
               </div>
+              <input
+                id="keyword"
+                type="text"
+                value={keyword}
+                onChange={(e) => setKeyword(e.target.value)}
+                onKeyDown={handleKeyDown}
+                placeholder="e.g., Algebra, JavaScript, Photography"
+                className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:ring-1 focus:ring-gray-500 focus:outline-hidden max-sm:placeholder:text-base"
+                maxLength={50}
+              />
             </div>
-
-            <div className="flex flex-col">
-              <label className="mb-2.5 text-sm font-medium text-gray-700">
-                Difficulty Level
-              </label>
-              <div className="flex gap-2 max-sm:flex-col max-sm:gap-1">
-                {difficultyLevels.map((level) => (
-                  <button
-                    key={level}
-                    type="button"
-                    onClick={() => setDifficulty(level)}
-                    className={cn(
-                      'rounded-md border px-4 py-2 capitalize max-sm:text-sm',
-                      difficulty === level
-                        ? 'border-gray-800 bg-gray-800 text-white'
-                        : 'border-gray-200 bg-gray-100 text-gray-700 hover:bg-gray-200',
-                    )}
-                  >
-                    {level}
-                  </button>
-                ))}
-              </div>
+          </div>
+
+          <div className="flex flex-col">
+            <label className="mb-2.5 text-sm font-medium text-gray-700">
+              Difficulty Level
+            </label>
+            <div className="flex gap-2 max-sm:flex-col max-sm:gap-1">
+              {difficultyLevels.map((level) => (
+                <button
+                  key={level}
+                  type="button"
+                  onClick={() => setDifficulty(level)}
+                  className={cn(
+                    'rounded-md border px-4 py-2 capitalize max-sm:text-sm',
+                    difficulty === level
+                      ? 'border-gray-800 bg-gray-800 text-white'
+                      : 'border-gray-200 bg-gray-100 text-gray-700 hover:bg-gray-200',
+                  )}
+                >
+                  {level}
+                </button>
+              ))}
             </div>
-
-            <FineTuneCourse
-              hasFineTuneData={hasFineTuneData}
-              setHasFineTuneData={setHasFineTuneData}
-              about={about}
-              goal={goal}
-              customInstructions={customInstructions}
-              setAbout={setAbout}
-              setGoal={setGoal}
-              setCustomInstructions={setCustomInstructions}
-            />
-
-            <button
-              type="submit"
-              disabled={!keyword.trim()}
-              className={cn(
-                'mt-2 flex items-center justify-center rounded-md px-4 py-2 font-medium text-white transition-colors max-sm:text-sm',
-                !keyword.trim()
-                  ? 'cursor-not-allowed bg-gray-400'
-                  : 'bg-black hover:bg-gray-800',
-              )}
-            >
-              <WandIcon size={18} className="mr-2" />
-              Generate Course
-            </button>
-          </form>
-        </div>
+          </div>
+
+          <FineTuneCourse
+            hasFineTuneData={hasFineTuneData}
+            setHasFineTuneData={setHasFineTuneData}
+            about={about}
+            goal={goal}
+            customInstructions={customInstructions}
+            setAbout={setAbout}
+            setGoal={setGoal}
+            setCustomInstructions={setCustomInstructions}
+          />
+
+          <button
+            type="submit"
+            disabled={!keyword.trim()}
+            className={cn(
+              'mt-2 flex items-center justify-center rounded-md px-4 py-2 font-medium text-white transition-colors max-sm:text-sm',
+              !keyword.trim()
+                ? 'cursor-not-allowed bg-gray-400'
+                : 'bg-black hover:bg-gray-800',
+            )}
+          >
+            <WandIcon size={18} className="mr-2" />
+            Generate Course
+          </button>
+        </form>
       </div>
-    </section>
+    </div>
   );
 }
diff --git a/src/components/GenerateCourse/AICourseCard.tsx b/src/components/GenerateCourse/AICourseCard.tsx
index 0d758978b..9db197881 100644
--- a/src/components/GenerateCourse/AICourseCard.tsx
+++ b/src/components/GenerateCourse/AICourseCard.tsx
@@ -12,14 +12,6 @@ type AICourseCardProps = {
 export function AICourseCard(props: AICourseCardProps) {
   const { course, showActions = true, showProgress = true } = props;
 
-  // Format date if available
-  const formattedDate = course.createdAt
-    ? new Date(course.createdAt).toLocaleDateString('en-US', {
-        month: 'short',
-        day: 'numeric',
-      })
-    : null;
-
   // Map difficulty to color
   const difficultyColor =
     {
@@ -35,10 +27,10 @@ export function AICourseCard(props: AICourseCardProps) {
     totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0;
 
   return (
-    <div className="relative">
+    <div className="relative flex flex-grow flex-col">
       <a
         href={`/ai/${course.slug}`}
-        className="hover:border-gray-3 00 group relative flex w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:bg-gray-50 min-h-full "
+        className="hover:border-gray-3 00 group relative flex h-full min-h-[140px] w-full flex-col overflow-hidden rounded-lg border border-gray-200 bg-white p-4 text-left transition-all hover:bg-gray-50"
       >
         <div className="flex items-center justify-between">
           <span
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index 798de8d5f..ee56d665a 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -7,7 +7,7 @@ import {
 import { queryClient } from '../../stores/query-client';
 import { AICourseCard } from './AICourseCard';
 import { useEffect, useState } from 'react';
-import { Gift, Loader2, User2 } from 'lucide-react';
+import { BookOpen, Gift } from 'lucide-react';
 import { isLoggedIn } from '../../lib/jwt';
 import { showLoginPopup } from '../../lib/popup';
 import { cn } from '../../lib/classname';
@@ -16,6 +16,8 @@ import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
 import { AICourseSearch } from './AICourseSearch';
 import { Pagination } from '../Pagination/Pagination';
+import { AILoadingState } from '../AITutor/AILoadingState';
+import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
 
 type UserCoursesListProps = {};
 
@@ -72,6 +74,43 @@ export function UserCoursesList(props: UserCoursesListProps) {
     }
   }, [pageState]);
 
+  if (isInitialLoading || isUserAiCoursesLoading) {
+    return (
+      <AILoadingState
+        title="Loading your courses"
+        subtitle="This may take a moment..."
+      />
+    );
+  }
+
+  if (!isLoggedIn()) {
+    return (
+      <AITutorTallMessage
+        title="Sign up or login"
+        subtitle="Takes 2s to sign up and generate your first course."
+        icon={BookOpen}
+        buttonText="Sign up or Login"
+        onButtonClick={() => {
+          showLoginPopup();
+        }}
+      />
+    );
+  }
+
+  if (courses.length === 0) {
+    return (
+      <AITutorTallMessage
+        title="No courses found"
+        subtitle="You haven't generated any courses yet."
+        icon={BookOpen}
+        buttonText="Create your first course"
+        onButtonClick={() => {
+          window.location.href = '/ai';
+        }}
+      />
+    );
+  }
+
   return (
     <>
       {showUpgradePopup && (
@@ -105,7 +144,7 @@ export function UserCoursesList(props: UserCoursesListProps) {
                   onClick={() => {
                     setShowUpgradePopup(true);
                   }}
-                  className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pl-1.5 pr-2 text-xs text-white"
+                  className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pr-2 pl-1.5 text-xs text-white"
                 >
                   <Gift className="size-4" />
                   Upgrade
@@ -127,46 +166,13 @@ export function UserCoursesList(props: UserCoursesListProps) {
         </div>
       </div>
 
-      {!isInitialLoading && !isUserAiCoursesLoading && !isAuthenticated && (
-        <div className="flex min-h-[152px] flex-col items-center justify-center rounded-lg border border-gray-200 bg-white px-6 py-4">
-          <User2 className="mb-2 size-8 text-gray-300" />
-          <p className="max-w-sm text-balance text-center text-gray-500">
-            <button
-              onClick={() => {
-                showLoginPopup();
-              }}
-              className="font-medium text-black underline underline-offset-2 hover:opacity-80"
-            >
-              Sign up (free and takes 2s) or login
-            </button>{' '}
-            to generate and save courses.
-          </p>
-        </div>
-      )}
-
-      {!isUserAiCoursesLoading && !isInitialLoading && courses.length === 0 && isAuthenticated && (
-        <div className="flex min-h-[152px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
-          <p className="text-sm text-gray-600">
-            You haven't generated any courses yet.
-          </p>
-        </div>
-      )}
-
-      {(isUserAiCoursesLoading || isInitialLoading) && (
-        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
-          <Loader2
-            className="size-4 animate-spin text-gray-400"
-            strokeWidth={2.5}
-          />
-          <p className="text-sm font-medium text-gray-600">Loading...</p>
-        </div>
-      )}
-
       {!isUserAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
-          {courses.map((course) => (
-            <AICourseCard key={course._id} course={course} />
-          ))}
+          <div className="grid grid-cols-3 gap-2">
+            {courses.map((course) => (
+              <AICourseCard key={course._id} course={course} />
+            ))}
+          </div>
 
           <Pagination
             totalCount={userAiCourses?.totalCount || 0}
diff --git a/src/pages/ai/courses.astro b/src/pages/ai/courses.astro
index e54254731..25a37a443 100644
--- a/src/pages/ai/courses.astro
+++ b/src/pages/ai/courses.astro
@@ -12,10 +12,6 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
   <AITutorLayout activeTab='courses' client:load>
-    <section class='flex grow flex-col bg-gray-100'>
-      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
-        <UserCoursesList client:load />
-      </div>
-    </section>
+    <UserCoursesList client:load />
   </AITutorLayout>
 </SkeletonLayout>

From bbe716cecf0262f358b87f6f4ac75e47f7ba0bcb Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 14:36:08 +0100
Subject: [PATCH 06/31] Update sidebar design

---
 src/components/AITutor/AITutorSidebar.tsx | 56 ++++++++++-------------
 src/pages/ai/staff-picks.astro            |  2 +-
 2 files changed, 24 insertions(+), 34 deletions(-)

diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 7ebc5006e..0f4efaf2a 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,10 +1,4 @@
-import {
-  ChevronLeft,
-  PlusCircle,
-  BookOpen,
-  Compass,
-  CircleDotIcon,
-} from 'lucide-react';
+import { BookOpen, Bot, Compass, Plus, Star } from 'lucide-react';
 
 type AITutorSidebarProps = {
   activeTab: AITutorTab;
@@ -15,7 +9,7 @@ const sidebarItems = [
     key: 'new',
     label: 'New Course',
     href: '/ai',
-    icon: PlusCircle,
+    icon: Plus,
   },
   {
     key: 'courses',
@@ -27,7 +21,7 @@ const sidebarItems = [
     key: 'staff-picks',
     label: 'Staff Picks',
     href: '/ai/staff-picks',
-    icon: CircleDotIcon,
+    icon: Star,
   },
   {
     key: 'explore',
@@ -43,38 +37,34 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
   const { activeTab } = props;
 
   return (
-    <div className="flex w-[240px] flex-col border-r border-gray-200 bg-gradient-to-b from-white to-gray-50">
-      <a
-        href="https://roadmap.sh"
-        className="flex w-full items-center justify-start gap-1.5 border-b border-gray-200 px-5 py-2 text-sm text-gray-600 transition-colors hover:bg-gray-100 hover:text-black"
-      >
-        <ChevronLeft className="size-4" />
-        Back to <span className="font-semibold text-black">roadmap.sh</span>
-      </a>
-
-      <div className="px-6 pt-6 pb-2">
-        <h2 className="text-lg font-semibold text-gray-900">Learn with AI</h2>
-        <p className="mt-1 text-sm text-gray-500">
+    <aside className="hidden w-[255px] shrink-0 border-r border-slate-200 md:block">
+      <div className="flex flex-col items-start justify-center px-6 py-5">
+        <Bot className="mb-2 size-8 text-black" />
+        <h2 className="mb-0.5 text-base font-semibold text-black">AI Tutor</h2>
+        <p className="max-w-[150px] text-xs text-gray-500">
           Your personalized learning companion for any topic
         </p>
       </div>
 
-      <div className="flex-1 px-3 py-3">
-        <nav className="space-y-1">
-          {sidebarItems.map((item) => (
+      <ul className="space-y-1">
+        {sidebarItems.map((item) => (
+          <li key={item.key}>
             <a
-              key={item.key}
               href={item.href}
-              className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium text-gray-700 transition-all hover:bg-gray-100 hover:text-black ${
-                activeTab === item.key ? 'bg-gray-100 text-black' : ''
+              className={`font-regular flex w-full items-center border-r-2 px-5 py-2 text-sm transition-all ${
+                activeTab === item.key
+                  ? 'border-r-black bg-gray-100 text-black'
+                  : 'border-r-transparent text-gray-500 hover:border-r-gray-300'
               }`}
             >
-              <item.icon className="size-4" />
-              {item.label}
+              <span className="flex grow items-center">
+                <item.icon className="mr-2 size-4" />
+                {item.label}
+              </span>
             </a>
-          ))}
-        </nav>
-      </div>
-    </div>
+          </li>
+        ))}
+      </ul>
+    </aside>
   );
 }
diff --git a/src/pages/ai/staff-picks.astro b/src/pages/ai/staff-picks.astro
index ad7ad5f74..840680333 100644
--- a/src/pages/ai/staff-picks.astro
+++ b/src/pages/ai/staff-picks.astro
@@ -11,7 +11,7 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   ogImageUrl={ogImage}
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
-  <AITutorLayout activeTab='stuff-picks' client:load>
+  <AITutorLayout activeTab='staff-picks' client:load>
     <section class='flex grow flex-col bg-gray-100'>
       <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
         <AIFeaturedCoursesListing client:load />

From 4cf3f052f76d04c8ac061f715260be4e967737f1 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 21:11:41 +0600
Subject: [PATCH 07/31] wip

---
 .../GenerateCourse/AICourseContent.tsx        | 52 ++++++++++--
 .../GenerateCourse/AICourseOutlineHeader.tsx  | 34 ++++++--
 .../GenerateCourse/AICourseOutlineView.tsx    |  6 ++
 .../GenerateCourse/ForkCourseAlert.tsx        | 34 ++++++++
 .../GenerateCourse/ForkCourseConfirmation.tsx | 84 +++++++++++++++++++
 src/components/GenerateCourse/GetAICourse.tsx |  1 +
 .../GenerateCourse/RegenerateOutline.tsx      | 30 +++++--
 7 files changed, 219 insertions(+), 22 deletions(-)
 create mode 100644 src/components/GenerateCourse/ForkCourseAlert.tsx
 create mode 100644 src/components/GenerateCourse/ForkCourseConfirmation.tsx

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index e0364fe33..6dc8aeec6 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -5,8 +5,9 @@ import {
   CircleOff,
   Menu,
   X,
-  Map, MessageCircleOffIcon,
-  MessageCircleIcon
+  Map,
+  MessageCircleOffIcon,
+  MessageCircleIcon,
 } from 'lucide-react';
 import { useEffect, useState } from 'react';
 import { type AiCourse } from '../../lib/ai';
@@ -21,6 +22,9 @@ import { AILimitsPopup } from './AILimitsPopup';
 import { AICourseOutlineView } from './AICourseOutlineView';
 import { AICourseRoadmapView } from './AICourseRoadmapView';
 import { AICourseFooter } from './AICourseFooter';
+import { ForkCourseAlert } from './ForkCourseAlert';
+import { ForkCourseConfirmation } from './ForkCourseConfirmation';
+import { useAuth } from '../../hooks/use-auth';
 
 type AICourseContentProps = {
   courseSlug?: string;
@@ -28,12 +32,20 @@ type AICourseContentProps = {
   isLoading: boolean;
   error?: string;
   onRegenerateOutline: (prompt?: string) => void;
+  creatorId?: string;
 };
 
 export type AICourseViewMode = 'module' | 'outline' | 'roadmap';
 
 export function AICourseContent(props: AICourseContentProps) {
-  const { course, courseSlug, isLoading, error, onRegenerateOutline } = props;
+  const {
+    course,
+    courseSlug,
+    isLoading,
+    error,
+    onRegenerateOutline,
+    creatorId,
+  } = props;
 
   const [showUpgradeModal, setShowUpgradeModal] = useState(false);
   const [showAILimitsPopup, setShowAILimitsPopup] = useState(false);
@@ -43,8 +55,10 @@ export function AICourseContent(props: AICourseContentProps) {
   const [activeLessonIndex, setActiveLessonIndex] = useState(0);
   const [sidebarOpen, setSidebarOpen] = useState(false);
   const [viewMode, setViewMode] = useState<AICourseViewMode>('outline');
+  const [isForkingCourse, setIsForkingCourse] = useState(false);
 
   const { isPaidUser } = useIsPaidUser();
+  const currentUser = useAuth();
 
   const aiCourseProgress = course.done || [];
 
@@ -202,7 +216,7 @@ export function AICourseContent(props: AICourseContentProps) {
             <div className="my-5">
               <a
                 href="/ai"
-                className="rounded-md bg-black px-6 py-2 text-sm font-medium text-white hover:bg-opacity-80"
+                className="hover:bg-opacity-80 rounded-md bg-black px-6 py-2 text-sm font-medium text-white"
               >
                 Create a course with AI
               </a>
@@ -214,6 +228,7 @@ export function AICourseContent(props: AICourseContentProps) {
   }
 
   const isViewingLesson = viewMode === 'module';
+  const isForkable = !!currentUser?.id && currentUser.id !== creatorId;
 
   return (
     <section className="flex h-screen grow flex-col overflow-hidden bg-gray-50">
@@ -272,7 +287,7 @@ export function AICourseContent(props: AICourseContentProps) {
       <header className="flex items-center justify-between border-b border-gray-200 bg-white px-6 max-lg:py-4 lg:h-[80px]">
         <div className="flex items-center">
           <div className="flex flex-col">
-            <h1 className="text-balance text-xl font-bold leading-tight! text-gray-900 max-lg:mb-0.5 max-lg:text-lg">
+            <h1 className="text-xl leading-tight! font-bold text-balance text-gray-900 max-lg:mb-0.5 max-lg:text-lg">
               {course.title || 'Loading Course...'}
             </h1>
             <div className="mt-1 flex flex-row items-center gap-2 text-sm text-gray-600 max-lg:text-xs">
@@ -342,7 +357,7 @@ export function AICourseContent(props: AICourseContentProps) {
                     width: `${finishedPercentage}%`,
                   }}
                   className={cn(
-                    'absolute bottom-0 left-0 top-0',
+                    'absolute top-0 bottom-0 left-0',
                     'bg-gray-200/50',
                   )}
                 ></span>
@@ -420,6 +435,27 @@ export function AICourseContent(props: AICourseContentProps) {
           )}
           key={`${courseSlug}-${viewMode}`}
         >
+          {isForkable &&
+            courseSlug &&
+            (viewMode === 'outline' || viewMode === 'roadmap') && (
+              <ForkCourseAlert
+                courseSlug={courseSlug}
+                creatorId={creatorId}
+                onForkCourse={() => {
+                  setIsForkingCourse(true);
+                }}
+              />
+            )}
+
+          {isForkingCourse && (
+            <ForkCourseConfirmation
+              onClose={() => {
+                setIsForkingCourse(false);
+              }}
+              courseSlug={courseSlug!}
+            />
+          )}
+
           {viewMode === 'module' && (
             <AICourseLesson
               courseSlug={courseSlug!}
@@ -450,6 +486,10 @@ export function AICourseContent(props: AICourseContentProps) {
               setViewMode={setViewMode}
               setExpandedModules={setExpandedModules}
               viewMode={viewMode}
+              isForkable={isForkable}
+              onForkCourse={() => {
+                setIsForkingCourse(true);
+              }}
             />
           )}
 
diff --git a/src/components/GenerateCourse/AICourseOutlineHeader.tsx b/src/components/GenerateCourse/AICourseOutlineHeader.tsx
index a841c99bb..386efc7cb 100644
--- a/src/components/GenerateCourse/AICourseOutlineHeader.tsx
+++ b/src/components/GenerateCourse/AICourseOutlineHeader.tsx
@@ -10,11 +10,20 @@ type AICourseOutlineHeaderProps = {
   onRegenerateOutline: (prompt?: string) => void;
   viewMode: AICourseViewMode;
   setViewMode: (mode: AICourseViewMode) => void;
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
-  const { course, isLoading, onRegenerateOutline, viewMode, setViewMode } =
-    props;
+  const {
+    course,
+    isLoading,
+    onRegenerateOutline,
+    viewMode,
+    setViewMode,
+    isForkable,
+    onForkCourse,
+  } = props;
 
   return (
     <div
@@ -24,18 +33,22 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
       )}
     >
       <div className="max-lg:hidden">
-        <h2 className="mb-1 text-balance text-2xl font-bold max-lg:text-lg max-lg:leading-tight">
+        <h2 className="mb-1 text-2xl font-bold text-balance max-lg:text-lg max-lg:leading-tight">
           {course.title || 'Loading course ..'}
         </h2>
-        <p className="text-sm capitalize text-gray-500">
+        <p className="text-sm text-gray-500 capitalize">
           {course.title ? course.difficulty : 'Please wait ..'}
         </p>
       </div>
 
-      <div className="absolute right-3 top-3 flex gap-2 max-lg:relative max-lg:right-0 max-lg:top-0 max-lg:w-full max-lg:flex-row-reverse max-lg:justify-between">
+      <div className="absolute top-3 right-3 flex gap-2 max-lg:relative max-lg:top-0 max-lg:right-0 max-lg:w-full max-lg:flex-row-reverse max-lg:justify-between">
         {!isLoading && (
           <>
-            <RegenerateOutline onRegenerateOutline={onRegenerateOutline} />
+            <RegenerateOutline
+              onRegenerateOutline={onRegenerateOutline}
+              isForkable={isForkable}
+              onForkCourse={onForkCourse}
+            />
             <div className="mr-1 flex rounded-lg border border-gray-200 bg-white p-0.5">
               <button
                 onClick={() => setViewMode('outline')}
@@ -55,7 +68,14 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
                 <span>Outline</span>
               </button>
               <button
-                onClick={() => setViewMode('roadmap')}
+                onClick={() => {
+                  if (isForkable) {
+                    onForkCourse();
+                    return;
+                  }
+
+                  setViewMode('roadmap');
+                }}
                 className={cn(
                   'flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors',
                   viewMode === 'roadmap'
diff --git a/src/components/GenerateCourse/AICourseOutlineView.tsx b/src/components/GenerateCourse/AICourseOutlineView.tsx
index 6aef4db96..75680f832 100644
--- a/src/components/GenerateCourse/AICourseOutlineView.tsx
+++ b/src/components/GenerateCourse/AICourseOutlineView.tsx
@@ -17,6 +17,8 @@ type AICourseOutlineViewProps = {
   setViewMode: (mode: AICourseViewMode) => void;
   setExpandedModules: Dispatch<SetStateAction<Record<number, boolean>>>;
   viewMode: AICourseViewMode;
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function AICourseOutlineView(props: AICourseOutlineViewProps) {
@@ -30,6 +32,8 @@ export function AICourseOutlineView(props: AICourseOutlineViewProps) {
     setViewMode,
     setExpandedModules,
     viewMode,
+    isForkable,
+    onForkCourse,
   } = props;
 
   const aiCourseProgress = course.done || [];
@@ -42,6 +46,8 @@ export function AICourseOutlineView(props: AICourseOutlineViewProps) {
         onRegenerateOutline={onRegenerateOutline}
         viewMode={viewMode}
         setViewMode={setViewMode}
+        isForkable={isForkable}
+        onForkCourse={onForkCourse}
       />
       {course.title ? (
         <div className="flex flex-col p-6 max-lg:mt-0.5 max-lg:p-4">
diff --git a/src/components/GenerateCourse/ForkCourseAlert.tsx b/src/components/GenerateCourse/ForkCourseAlert.tsx
new file mode 100644
index 000000000..e7280b94a
--- /dev/null
+++ b/src/components/GenerateCourse/ForkCourseAlert.tsx
@@ -0,0 +1,34 @@
+import { GitForkIcon } from 'lucide-react';
+import { getUser } from '../../lib/jwt';
+
+type ForkCourseAlertProps = {
+  courseSlug: string;
+  creatorId?: string;
+  onForkCourse: () => void;
+};
+
+export function ForkCourseAlert(props: ForkCourseAlertProps) {
+  const { courseSlug, creatorId, onForkCourse } = props;
+
+  const currentUser = getUser();
+
+  if (!currentUser || !creatorId || currentUser?.id === creatorId) {
+    return null;
+  }
+
+  return (
+    <div className="mb-4 flex items-center justify-between gap-2 rounded-lg bg-yellow-200 p-3 text-black">
+      <p className="text-sm text-balance">
+        To start tracking your progress, you can fork the course.
+      </p>
+
+      <button
+        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 p-1 px-2 text-sm text-black"
+        onClick={onForkCourse}
+      >
+        <GitForkIcon className="size-3.5" />
+        Fork Course
+      </button>
+    </div>
+  );
+}
diff --git a/src/components/GenerateCourse/ForkCourseConfirmation.tsx b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
new file mode 100644
index 000000000..b0c19f67b
--- /dev/null
+++ b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
@@ -0,0 +1,84 @@
+import { GitForkIcon, Loader2Icon } from 'lucide-react';
+import { Modal } from '../Modal';
+import type { AICourseDocument } from '../../queries/ai-course';
+import { useMutation } from '@tanstack/react-query';
+import { queryClient } from '../../stores/query-client';
+import { httpPost } from '../../lib/query-http';
+import { useToast } from '../../hooks/use-toast';
+import { useState } from 'react';
+
+type ForkAICourseParams = {
+  aiCourseSlug: string;
+};
+
+type ForkAICourseBody = {};
+
+type ForkAICourseQuery = {};
+
+type ForkAICourseResponse = AICourseDocument;
+
+type ForkCourseConfirmationProps = {
+  onClose: () => void;
+  courseSlug: string;
+};
+
+export function ForkCourseConfirmation(props: ForkCourseConfirmationProps) {
+  const { onClose, courseSlug } = props;
+
+  const toast = useToast();
+  const [isPending, setIsPending] = useState(false);
+  const { mutate: forkCourse } = useMutation(
+    {
+      mutationFn: async () => {
+        setIsPending(true);
+        return httpPost(
+          `${import.meta.env.PUBLIC_API_URL}/v1-fork-ai-course/${courseSlug}`,
+          {},
+        );
+      },
+      onSuccess(data) {
+        window.location.href = `/ai/${data.slug}`;
+      },
+      onError(error) {
+        toast.error(error?.message || 'Failed to fork course');
+        setIsPending(false);
+      },
+    },
+    queryClient,
+  );
+
+  return (
+    <Modal onClose={isPending ? () => {} : onClose}>
+      <div className="flex flex-col items-center p-4 pt-8">
+        <GitForkIcon className="size-14 text-gray-500" />
+        <p className="mt-2 text-xl font-medium">Fork Course</p>
+        <p className="mt-1 text-center text-balance text-gray-500">
+          Forking this course will create a new course with the same content.
+        </p>
+
+        <div className="mt-4 grid w-full grid-cols-2 gap-2">
+          <button
+            disabled={isPending}
+            className="flex items-center justify-center gap-2 rounded-md bg-gray-100 p-2 hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
+          >
+            Cancel
+          </button>
+
+          <button
+            disabled={isPending}
+            className="flex h-10 items-center justify-center gap-2 rounded-md bg-black p-2 text-white hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
+            onClick={() => {
+              forkCourse();
+            }}
+          >
+            {isPending ? (
+              <Loader2Icon className="size-4 animate-spin" />
+            ) : (
+              'Fork Course'
+            )}
+          </button>
+        </div>
+      </div>
+    </Modal>
+  );
+}
diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx
index e9b1c98a3..dfe45de9f 100644
--- a/src/components/GenerateCourse/GetAICourse.tsx
+++ b/src/components/GenerateCourse/GetAICourse.tsx
@@ -102,6 +102,7 @@ export function GetAICourse(props: GetAICourseProps) {
       courseSlug={courseSlug}
       error={error}
       onRegenerateOutline={handleRegenerateCourse}
+      creatorId={aiCourse?.userId}
     />
   );
 }
diff --git a/src/components/GenerateCourse/RegenerateOutline.tsx b/src/components/GenerateCourse/RegenerateOutline.tsx
index 57634af2f..45005c4ef 100644
--- a/src/components/GenerateCourse/RegenerateOutline.tsx
+++ b/src/components/GenerateCourse/RegenerateOutline.tsx
@@ -7,10 +7,12 @@ import { ModifyCoursePrompt } from './ModifyCoursePrompt';
 
 type RegenerateOutlineProps = {
   onRegenerateOutline: (prompt?: string) => void;
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function RegenerateOutline(props: RegenerateOutlineProps) {
-  const { onRegenerateOutline } = props;
+  const { onRegenerateOutline, isForkable, onForkCourse } = props;
 
   const [isDropdownVisible, setIsDropdownVisible] = useState(false);
   const [showUpgradeModal, setShowUpgradeModal] = useState(false);
@@ -35,27 +37,33 @@ export function RegenerateOutline(props: RegenerateOutlineProps) {
           onClose={() => setShowPromptModal(false)}
           onSubmit={(prompt) => {
             setShowPromptModal(false);
+            if (isForkable) {
+              onForkCourse();
+              return;
+            }
             onRegenerateOutline(prompt);
           }}
         />
       )}
 
-      <div ref={ref} className="flex relative items-stretch">
+      <div ref={ref} className="relative flex items-stretch">
         <button
-          className={cn(
-            'rounded-md px-2.5 text-gray-400 hover:text-black',
-            {
-              'text-black': isDropdownVisible,
-            },
-          )}
+          className={cn('rounded-md px-2.5 text-gray-400 hover:text-black', {
+            'text-black': isDropdownVisible,
+          })}
           onClick={() => setIsDropdownVisible(!isDropdownVisible)}
         >
           <PenSquare className="text-current" size={16} strokeWidth={2.5} />
         </button>
         {isDropdownVisible && (
-          <div className="absolute right-0 top-full translate-y-1 min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
+          <div className="absolute top-full right-0 min-w-[170px] translate-y-1 overflow-hidden rounded-md border border-gray-200 bg-white shadow-md">
             <button
               onClick={() => {
+                setIsDropdownVisible(false);
+                if (isForkable) {
+                  onForkCourse();
+                  return;
+                }
                 onRegenerateOutline();
               }}
               className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
@@ -70,6 +78,10 @@ export function RegenerateOutline(props: RegenerateOutlineProps) {
             <button
               onClick={() => {
                 setIsDropdownVisible(false);
+                if (isForkable) {
+                  onForkCourse();
+                  return;
+                }
                 setShowPromptModal(true);
               }}
               className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"

From 653778b13d5b01d3daaaee69992386a534ac3096 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 21:14:16 +0600
Subject: [PATCH 08/31] wip

---
 .../GenerateCourse/AICourseContent.tsx        |  4 +++
 .../GenerateCourse/AICourseLesson.tsx         | 32 +++++++++++++------
 .../GenerateCourse/ModifyCoursePrompt.tsx     | 17 ++++++----
 .../GenerateCourse/RegenerateLesson.tsx       | 22 +++++++++++--
 4 files changed, 57 insertions(+), 18 deletions(-)

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index 6dc8aeec6..e2abc32dc 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -472,6 +472,10 @@ export function AICourseContent(props: AICourseContentProps) {
               onUpgrade={() => setShowUpgradeModal(true)}
               isAIChatsOpen={isAIChatsOpen}
               setIsAIChatsOpen={setIsAIChatsOpen}
+              isForkable={isForkable}
+              onForkCourse={() => {
+                setIsForkingCourse(true);
+              }}
             />
           )}
 
diff --git a/src/components/GenerateCourse/AICourseLesson.tsx b/src/components/GenerateCourse/AICourseLesson.tsx
index 3264c729d..3a10b5680 100644
--- a/src/components/GenerateCourse/AICourseLesson.tsx
+++ b/src/components/GenerateCourse/AICourseLesson.tsx
@@ -70,6 +70,9 @@ type AICourseLessonProps = {
 
   isAIChatsOpen: boolean;
   setIsAIChatsOpen: (isOpen: boolean) => void;
+
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function AICourseLesson(props: AICourseLessonProps) {
@@ -91,6 +94,9 @@ export function AICourseLesson(props: AICourseLessonProps) {
 
     isAIChatsOpen,
     setIsAIChatsOpen,
+
+    isForkable,
+    onForkCourse,
   } = props;
 
   const [isLoading, setIsLoading] = useState(true);
@@ -108,8 +114,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
   >([
     {
       role: 'assistant',
-      content:
-        'Hey, I am your AI instructor. How can I help you today? 🤖',
+      content: 'Hey, I am your AI instructor. How can I help you today? 🤖',
       isDefault: true,
     },
   ]);
@@ -205,7 +210,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
 
           const questions = getQuestionsFromResult(result);
           setDefaultQuestions(questions);
-          
+
           const newResult = result.replace(
             /=START_QUESTIONS=.*?=END_QUESTIONS=/,
             '',
@@ -284,7 +289,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
           <div className="relative mx-auto max-w-5xl">
             <div className="bg-white p-8 pb-0 max-lg:px-4 max-lg:pt-3">
               {(isGenerating || isLoading) && (
-                <div className="absolute right-6 top-6 flex items-center justify-center">
+                <div className="absolute top-6 right-6 flex items-center justify-center">
                   <Loader2Icon
                     size={18}
                     strokeWidth={3}
@@ -299,7 +304,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
                 </div>
 
                 {!isGenerating && !isLoading && (
-                  <div className="absolute top-2 right-2 lg:right-6 lg:top-6 flex items-center justify-between gap-2">
+                  <div className="absolute top-2 right-2 flex items-center justify-between gap-2 lg:top-6 lg:right-6">
                     <button
                       onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
                       className="rounded-full p-1 text-gray-400 hover:text-black max-lg:hidden"
@@ -315,16 +320,25 @@ export function AICourseLesson(props: AICourseLessonProps) {
                       onRegenerateLesson={(prompt) => {
                         generateAiCourseContent(true, prompt);
                       }}
+                      isForkable={isForkable}
+                      onForkCourse={onForkCourse}
                     />
                     <button
                       disabled={isLoading || isTogglingDone}
                       className={cn(
-                        'flex items-center gap-1.5 rounded-full bg-black py-1 pl-2 pr-3 text-sm text-white hover:bg-gray-800 disabled:opacity-50 max-lg:text-xs',
+                        'flex items-center gap-1.5 rounded-full bg-black py-1 pr-3 pl-2 text-sm text-white hover:bg-gray-800 disabled:opacity-50 max-lg:text-xs',
                         isLessonDone
                           ? 'bg-red-500 hover:bg-red-600'
                           : 'bg-green-500 hover:bg-green-600',
                       )}
-                      onClick={() => toggleDone()}
+                      onClick={() => {
+                        if (isForkable) {
+                          onForkCourse();
+                          return;
+                        }
+
+                        toggleDone();
+                      }}
                     >
                       {isTogglingDone ? (
                         <>
@@ -355,13 +369,13 @@ export function AICourseLesson(props: AICourseLessonProps) {
                 )}
               </div>
 
-              <h1 className="mb-6 text-balance text-3xl font-semibold max-lg:mb-3 max-lg:text-xl">
+              <h1 className="mb-6 text-3xl font-semibold text-balance max-lg:mb-3 max-lg:text-xl">
                 {currentLessonTitle?.replace(/^Lesson\s*?\d+[\.:]\s*/, '')}
               </h1>
 
               {!error && isLoggedIn() && (
                 <div
-                  className="course-content prose prose-lg mt-8 max-w-full text-black prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:mt-4 max-lg:text-base max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm"
+                  className="course-content prose prose-lg prose-headings:mb-3 prose-headings:mt-8 prose-blockquote:font-normal prose-pre:rounded-2xl prose-pre:text-lg prose-li:my-1 prose-thead:border-zinc-800 prose-tr:border-zinc-800 max-lg:prose-h2:mt-3 max-lg:prose-h2:text-lg max-lg:prose-h3:text-base max-lg:prose-pre:px-3 max-lg:prose-pre:text-sm mt-8 max-w-full text-black max-lg:mt-4 max-lg:text-base"
                   dangerouslySetInnerHTML={{ __html: lessonHtml }}
                 />
               )}
diff --git a/src/components/GenerateCourse/ModifyCoursePrompt.tsx b/src/components/GenerateCourse/ModifyCoursePrompt.tsx
index 35583635d..94824f822 100644
--- a/src/components/GenerateCourse/ModifyCoursePrompt.tsx
+++ b/src/components/GenerateCourse/ModifyCoursePrompt.tsx
@@ -4,10 +4,17 @@ import { Modal } from '../Modal';
 export type ModifyCoursePromptProps = {
   onClose: () => void;
   onSubmit: (prompt: string) => void;
+  title?: string;
+  description?: string;
 };
 
 export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
-  const { onClose, onSubmit } = props;
+  const {
+    onClose,
+    onSubmit,
+    title = 'Give AI more context',
+    description = 'Pass additional information to the AI to generate a course outline.',
+  } = props;
 
   const [prompt, setPrompt] = useState('');
 
@@ -25,12 +32,8 @@ export function ModifyCoursePrompt(props: ModifyCoursePromptProps) {
     >
       <div className="flex flex-col gap-4">
         <div>
-          <h2 className="mb-2 text-left text-xl font-semibold">
-            Give AI more context
-          </h2>
-          <p className="text-sm text-gray-500">
-            Pass additional information to the AI to generate a course outline.
-          </p>
+          <h2 className="mb-2 text-left text-xl font-semibold">{title}</h2>
+          <p className="text-sm text-gray-500">{description}</p>
         </div>
         <form className="flex flex-col gap-2" onSubmit={handleSubmit}>
           <textarea
diff --git a/src/components/GenerateCourse/RegenerateLesson.tsx b/src/components/GenerateCourse/RegenerateLesson.tsx
index 1323aa551..2427c119c 100644
--- a/src/components/GenerateCourse/RegenerateLesson.tsx
+++ b/src/components/GenerateCourse/RegenerateLesson.tsx
@@ -7,10 +7,12 @@ import { ModifyCoursePrompt } from './ModifyCoursePrompt';
 
 type RegenerateLessonProps = {
   onRegenerateLesson: (prompt?: string) => void;
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function RegenerateLesson(props: RegenerateLessonProps) {
-  const { onRegenerateLesson } = props;
+  const { onRegenerateLesson, isForkable, onForkCourse } = props;
 
   const [isDropdownVisible, setIsDropdownVisible] = useState(false);
   const [showUpgradeModal, setShowUpgradeModal] = useState(false);
@@ -37,6 +39,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
           onClose={() => setShowPromptModal(false)}
           onSubmit={(prompt) => {
             setShowPromptModal(false);
+            if (isForkable) {
+              onForkCourse();
+              return;
+            }
+
             onRegenerateLesson(prompt);
           }}
         />
@@ -52,9 +59,15 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
           <PenSquare className="text-current" size={16} strokeWidth={2.5} />
         </button>
         {isDropdownVisible && (
-          <div className="absolute right-0 top-full min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
+          <div className="absolute top-full right-0 min-w-[170px] overflow-hidden rounded-md border border-gray-200 bg-white">
             <button
               onClick={() => {
+                setIsDropdownVisible(false);
+                if (isForkable) {
+                  onForkCourse();
+                  return;
+                }
+
                 onRegenerateLesson();
               }}
               className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"
@@ -69,6 +82,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
             <button
               onClick={() => {
                 setIsDropdownVisible(false);
+                if (isForkable) {
+                  onForkCourse();
+                  return;
+                }
+
                 setShowPromptModal(true);
               }}
               className="flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-gray-600 hover:bg-gray-100"

From f95ef58a93b3e6d9616a47d203fe15eadc601348 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 16:31:55 +0100
Subject: [PATCH 09/31] Update AI tutor sidebar

---
 src/components/AITutor/AITutorSidebar.tsx | 14 +++++++--
 src/components/ReactIcons/AITutorLogo.tsx | 36 +++++++++++++++++++++++
 2 files changed, 47 insertions(+), 3 deletions(-)
 create mode 100644 src/components/ReactIcons/AITutorLogo.tsx

diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 0f4efaf2a..2f4e05972 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,4 +1,5 @@
-import { BookOpen, Bot, Compass, Plus, Star } from 'lucide-react';
+import { BookOpen, Compass, Plus, Star } from 'lucide-react';
+import { AITutorLogo } from '../ReactIcons/AITutorLogo';
 
 type AITutorSidebarProps = {
   activeTab: AITutorTab;
@@ -39,8 +40,15 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
   return (
     <aside className="hidden w-[255px] shrink-0 border-r border-slate-200 md:block">
       <div className="flex flex-col items-start justify-center px-6 py-5">
-        <Bot className="mb-2 size-8 text-black" />
-        <h2 className="mb-0.5 text-base font-semibold text-black">AI Tutor</h2>
+        <div className="flex flex-row items-center gap-1">
+          <AITutorLogo className="size-11 text-gray-500" color="black" />
+        </div>
+        <div className="my-3 flex flex-col">
+          <h2 className="-mb-px text-base font-semibold text-black">
+            AI Tutor
+          </h2>
+          <span className="text-xs text-gray-500">by roadmap.sh</span>
+        </div>
         <p className="max-w-[150px] text-xs text-gray-500">
           Your personalized learning companion for any topic
         </p>
diff --git a/src/components/ReactIcons/AITutorLogo.tsx b/src/components/ReactIcons/AITutorLogo.tsx
new file mode 100644
index 000000000..0ddac87dd
--- /dev/null
+++ b/src/components/ReactIcons/AITutorLogo.tsx
@@ -0,0 +1,36 @@
+import type { SVGProps } from 'react';
+
+type AITutorLogoProps = SVGProps<SVGSVGElement>;
+
+export function AITutorLogo(props: AITutorLogoProps) {
+  return (
+    <svg
+      viewBox="0 0 310 248"
+      fill="none"
+      xmlns="http://www.w3.org/2000/svg"
+      {...props}
+    >
+      <rect width="310" height="247.211" fill="black" />
+      <path
+        d="M205.179 45.1641H263.851V201.278H205.179V45.1641Z"
+        fill="white"
+      />
+      <path
+        d="M45.1641 45.1743H104.598L104.598 202.048H45.1641L45.1641 45.1743Z"
+        fill="white"
+      />
+      <path
+        d="M160.984 45.1743V103.716L45.1641 103.716L45.1641 45.1743H160.984Z"
+        fill="white"
+      />
+      <path
+        d="M125.171 45.1743H184.605V201.284H125.171V45.1743Z"
+        fill="white"
+      />
+      <path
+        d="M159.841 131.85V173.88L63.8324 173.88L63.8324 131.85H159.841Z"
+        fill="white"
+      />
+    </svg>
+  );
+}

From 0c6c6e0246c1cb73a5d8e6638bb388e45d7d84ef Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 21:34:14 +0600
Subject: [PATCH 10/31] wip

---
 src/components/GenerateCourse/AICourseContent.tsx | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index e2abc32dc..820b33197 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -8,6 +8,7 @@ import {
   Map,
   MessageCircleOffIcon,
   MessageCircleIcon,
+  GitForkIcon,
 } from 'lucide-react';
 import { useEffect, useState } from 'react';
 import { type AiCourse } from '../../lib/ai';
@@ -327,6 +328,17 @@ export function AICourseContent(props: AICourseContentProps) {
               onUpgrade={() => setShowUpgradeModal(true)}
               onShowLimits={() => setShowAILimitsPopup(true)}
             />
+            {isForkable && (
+              <button
+                className="hidden items-center justify-center gap-1 rounded-md bg-yellow-400 px-4 py-1 text-sm font-medium underline-offset-2 hover:bg-yellow-500 lg:flex"
+                onClick={() => {
+                  setIsForkingCourse(true);
+                }}
+              >
+                <GitForkIcon className="size-4" />
+                Fork
+              </button>
+            )}
           </div>
         </div>
       </header>

From 204a421559762d1d1976e83749965e3c687caeff Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 16:55:38 +0100
Subject: [PATCH 11/31] Add ai-course dropdown

---
 src/components/AITutor/AITutorLayout.tsx      |  2 +-
 src/components/AITutor/DifficultyDropdown.tsx | 69 +++++++++++++++++++
 src/components/GenerateCourse/AICourse.tsx    | 65 +++++------------
 .../GenerateCourse/FineTuneCourse.tsx         |  2 +-
 4 files changed, 88 insertions(+), 50 deletions(-)
 create mode 100644 src/components/AITutor/DifficultyDropdown.tsx

diff --git a/src/components/AITutor/AITutorLayout.tsx b/src/components/AITutor/AITutorLayout.tsx
index 5cfe7f3ce..c7663c48f 100644
--- a/src/components/AITutor/AITutorLayout.tsx
+++ b/src/components/AITutor/AITutorLayout.tsx
@@ -11,7 +11,7 @@ export function AITutorLayout(props: AITutorLayoutProps) {
   return (
     <div className="flex flex-grow flex-row">
       <AITutorSidebar activeTab={activeTab} />
-      <div className="flex flex-grow flex-col bg-gray-100 px-4 py-4">
+      <div className="flex flex-grow h-screen overflow-y-scroll flex-col bg-gray-100 px-4 py-4">
         {children}
       </div>
     </div>
diff --git a/src/components/AITutor/DifficultyDropdown.tsx b/src/components/AITutor/DifficultyDropdown.tsx
new file mode 100644
index 000000000..0f5320090
--- /dev/null
+++ b/src/components/AITutor/DifficultyDropdown.tsx
@@ -0,0 +1,69 @@
+import { ChevronDown } from 'lucide-react';
+import { useState, useRef, useEffect } from 'react';
+import { cn } from '../../lib/classname';
+import {
+  difficultyLevels,
+  type DifficultyLevel,
+} from '../GenerateCourse/AICourse';
+
+type DifficultyDropdownProps = {
+  value: DifficultyLevel;
+  onChange: (value: DifficultyLevel) => void;
+};
+
+export function DifficultyDropdown(props: DifficultyDropdownProps) {
+  const { value, onChange } = props;
+
+  const [isOpen, setIsOpen] = useState(false);
+  const dropdownRef = useRef<HTMLDivElement>(null);
+
+  useEffect(() => {
+    function handleClickOutside(event: MouseEvent) {
+      if (
+        dropdownRef.current &&
+        !dropdownRef.current.contains(event.target as Node)
+      ) {
+        setIsOpen(false);
+      }
+    }
+
+    document.addEventListener('mousedown', handleClickOutside);
+    return () => document.removeEventListener('mousedown', handleClickOutside);
+  }, []);
+
+  return (
+    <div className="relative" ref={dropdownRef}>
+      <button
+        type="button"
+        onClick={() => setIsOpen(!isOpen)}
+        className={cn(
+          'flex items-center gap-2 rounded-full bg-gray-100 px-3 py-1 text-sm text-gray-700 hover:bg-gray-200 hover:text-black',
+        )}
+      >
+        <span className="capitalize">{value}</span>
+        <ChevronDown size={16} className={cn(isOpen && 'rotate-180')} />
+      </button>
+
+      {isOpen && (
+        <div className="absolute z-10 mt-1 flex flex-col overflow-hidden rounded-md border border-gray-200 bg-white shadow-lg">
+          {difficultyLevels.map((level) => (
+            <button
+              key={level}
+              type="button"
+              onClick={() => {
+                onChange(level);
+                setIsOpen(false);
+              }}
+              className={cn(
+                'px-5 py-2 text-left text-sm capitalize hover:bg-gray-100',
+                value === level && 'bg-gray-200 font-medium hover:bg-gray-200',
+              )}
+            >
+              {level}
+            </button>
+          ))}
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index 428fb21fe..6fa957a42 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -1,9 +1,10 @@
-import { SearchIcon, WandIcon } from 'lucide-react';
+import { WandIcon } from 'lucide-react';
 import { useEffect, useState } from 'react';
 import { cn } from '../../lib/classname';
 import { isLoggedIn } from '../../lib/jwt';
 import { showLoginPopup } from '../../lib/popup';
 import { FineTuneCourse } from './FineTuneCourse';
+import { DifficultyDropdown } from '../AITutor/DifficultyDropdown';
 import {
   clearFineTuneData,
   getCourseFineTuneData,
@@ -71,7 +72,7 @@ export function AICourse(props: AICourseProps) {
   }
 
   return (
-    <div className="flex w-full max-w-3xl mx-auto flex-grow flex-col justify-center">
+    <div className="mx-auto flex w-full max-w-3xl flex-grow flex-col justify-center">
       <h1 className="mb-2.5 text-center text-4xl font-bold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
         What can I help you learn?
       </h1>
@@ -79,59 +80,27 @@ export function AICourse(props: AICourseProps) {
         Enter a topic below to generate a personalized course for it
       </p>
 
-      <div className="rounded-lg border border-gray-200 bg-white p-6 max-sm:p-4">
+      <div className="rounded-lg border border-gray-300 bg-white">
         <form
-          className="flex flex-col gap-5"
+          className="flex flex-col"
           onSubmit={(e) => {
             e.preventDefault();
             onSubmit();
           }}
         >
-          <div className="flex flex-col">
-            <label
-              htmlFor="keyword"
-              className="mb-2.5 text-sm font-medium text-gray-700"
-            >
-              Course Topic
-            </label>
-            <div className="relative">
-              <div className="absolute top-1/2 left-3 -translate-y-1/2 text-gray-400">
-                <SearchIcon size={18} />
-              </div>
-              <input
-                id="keyword"
-                type="text"
-                value={keyword}
-                onChange={(e) => setKeyword(e.target.value)}
-                onKeyDown={handleKeyDown}
-                placeholder="e.g., Algebra, JavaScript, Photography"
-                className="w-full rounded-md border border-gray-300 bg-white p-3 pl-10 text-gray-900 focus:ring-1 focus:ring-gray-500 focus:outline-hidden max-sm:placeholder:text-base"
-                maxLength={50}
-              />
-            </div>
-          </div>
+          <input
+            id="keyword"
+            type="text"
+            value={keyword}
+            onChange={(e) => setKeyword(e.target.value)}
+            onKeyDown={handleKeyDown}
+            placeholder="Ask tutor to teach you..."
+            className="w-full rounded-md border-none bg-transparent px-4 pt-4 pb-8 text-gray-900 focus:outline-hidden max-sm:placeholder:text-base"
+            maxLength={50}
+          />
 
-          <div className="flex flex-col">
-            <label className="mb-2.5 text-sm font-medium text-gray-700">
-              Difficulty Level
-            </label>
-            <div className="flex gap-2 max-sm:flex-col max-sm:gap-1">
-              {difficultyLevels.map((level) => (
-                <button
-                  key={level}
-                  type="button"
-                  onClick={() => setDifficulty(level)}
-                  className={cn(
-                    'rounded-md border px-4 py-2 capitalize max-sm:text-sm',
-                    difficulty === level
-                      ? 'border-gray-800 bg-gray-800 text-white'
-                      : 'border-gray-200 bg-gray-100 text-gray-700 hover:bg-gray-200',
-                  )}
-                >
-                  {level}
-                </button>
-              ))}
-            </div>
+          <div className="flex flex-col px-4">
+            <DifficultyDropdown value={difficulty} onChange={setDifficulty} />
           </div>
 
           <FineTuneCourse
diff --git a/src/components/GenerateCourse/FineTuneCourse.tsx b/src/components/GenerateCourse/FineTuneCourse.tsx
index e1641ca59..0d6cef085 100644
--- a/src/components/GenerateCourse/FineTuneCourse.tsx
+++ b/src/components/GenerateCourse/FineTuneCourse.tsx
@@ -52,7 +52,7 @@ export function FineTuneCourse(props: FineTuneCourseProps) {
   } = props;
 
   return (
-    <div className="flex flex-col overflow-hidden rounded-lg border border-gray-200 transition-all">
+    <div className="flex flex-col overflow-hidden transition-all">
       <label
         className={cn(
           'group flex cursor-pointer select-none flex-row items-center gap-2.5 px-4 py-3 text-left text-gray-500 transition-colors hover:bg-gray-100 focus:outline-hidden',

From e660d9da15b64c26130f0f4a008735a28799a98f Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 17:07:21 +0100
Subject: [PATCH 12/31] Update

---
 src/components/AITutor/AITutorSidebar.tsx  |  4 +++-
 src/components/GenerateCourse/AICourse.tsx | 16 +++++++++++++---
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 2f4e05972..c4166a676 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -47,7 +47,9 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
           <h2 className="-mb-px text-base font-semibold text-black">
             AI Tutor
           </h2>
-          <span className="text-xs text-gray-500">by roadmap.sh</span>
+          <span className="text-xs text-gray-500">
+            by <a href="/" className="hover:underline underline-offset-2">roadmap.sh</a>
+          </span>
         </div>
         <p className="max-w-[150px] text-xs text-gray-500">
           Your personalized learning companion for any topic
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index 6fa957a42..b002f641f 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -1,4 +1,4 @@
-import { WandIcon } from 'lucide-react';
+import { Settings2Icon, WandIcon } from 'lucide-react';
 import { useEffect, useState } from 'react';
 import { cn } from '../../lib/classname';
 import { isLoggedIn } from '../../lib/jwt';
@@ -99,8 +99,18 @@ export function AICourse(props: AICourseProps) {
             maxLength={50}
           />
 
-          <div className="flex flex-col px-4">
-            <DifficultyDropdown value={difficulty} onChange={setDifficulty} />
+          <div className="flex flex-row gap-2 px-4">
+            <div className="flex flex-row gap-2">
+              <DifficultyDropdown value={difficulty} onChange={setDifficulty} />
+            </div>
+            <button
+              type="button"
+              onClick={() => setHasFineTuneData(!hasFineTuneData)}
+              className="flex px-2 py-1 bg-gray-100 rounded-full flex-row items-center gap-1 text-sm text-gray-500 hover:text-gray-700"
+            >
+              <Settings2Icon size={16} />
+              Settings
+            </button>
           </div>
 
           <FineTuneCourse

From 0f29273ff334efacdc06181906479e3d06714430 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Wed, 9 Apr 2025 22:15:22 +0600
Subject: [PATCH 13/31] fix: ai chat window position

---
 .../GenerateCourse/AICourseLessonChat.tsx         | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/components/GenerateCourse/AICourseLessonChat.tsx b/src/components/GenerateCourse/AICourseLessonChat.tsx
index b0da9256d..4e6fc081e 100644
--- a/src/components/GenerateCourse/AICourseLessonChat.tsx
+++ b/src/components/GenerateCourse/AICourseLessonChat.tsx
@@ -221,23 +221,26 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
       minSize={20}
       id="course-chat-content"
       order={2}
-      className="relative h-full max-lg:fixed max-lg:inset-0 max-lg:data-[chat-state=open]:flex max-lg:data-[chat-state=closed]:hidden"
+      className="relative h-full max-lg:fixed! max-lg:inset-0! max-lg:data-[chat-state=closed]:hidden max-lg:data-[chat-state=open]:flex"
       data-chat-state={isAIChatsOpen ? 'open' : 'closed'}
     >
       <div
-        className="absolute inset-y-0 right-0 z-20 flex w-full flex-col overflow-hidden bg-white data-[state=open]:flex data-[state=closed]:hidden"
+        className="absolute inset-y-0 right-0 z-20 flex w-full flex-col overflow-hidden bg-white data-[state=closed]:hidden data-[state=open]:flex"
         data-state={isAIChatsOpen ? 'open' : 'closed'}
       >
         <button
           onClick={onClose}
-          className="absolute right-2 top-2 z-20 hidden rounded-full p-1 text-gray-400 hover:text-black max-lg:block"
+          className="absolute top-2 right-2 z-20 hidden rounded-full p-1 text-gray-400 hover:text-black max-lg:block"
         >
           <XIcon className="size-4 stroke-[2.5]" />
         </button>
 
         <div className="flex items-center justify-between gap-2 border-b border-gray-200 px-4 py-2 text-sm">
           <h4 className="flex items-center gap-2 text-base font-medium">
-            <Bot className="size-5 shrink-0 text-black relative -top-[1px]" strokeWidth={2.5} />
+            <Bot
+              className="relative -top-[1px] size-5 shrink-0 text-black"
+              strokeWidth={2.5}
+            />
             AI Instructor
           </h4>
           <button
@@ -278,7 +281,7 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
                       />
 
                       {chat.isDefault && defaultQuestions?.length > 1 && (
-                        <div className="mb-1 mt-0.5">
+                        <div className="mt-0.5 mb-1">
                           <p className="mb-2 text-xs font-normal text-gray-500">
                             Some questions you might have about this lesson.
                           </p>
@@ -442,7 +445,7 @@ function CapabilityCard({
     >
       <div className="flex items-center gap-2">
         {icon}
-        <span className="text-[13px] font-medium leading-none text-black">
+        <span className="text-[13px] leading-none font-medium text-black">
           {title}
         </span>
       </div>

From 150d38af2b69b3e121642715657586d74adc6952 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 17:34:27 +0100
Subject: [PATCH 14/31] Course explanation changes

---
 src/components/GenerateCourse/AICourse.tsx    | 60 +++++++++-------
 .../GenerateCourse/FineTuneCourse.tsx         | 71 +++++++------------
 2 files changed, 59 insertions(+), 72 deletions(-)

diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index b002f641f..5f01d6733 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -1,6 +1,5 @@
-import { Settings2Icon, WandIcon } from 'lucide-react';
+import { WandIcon } from 'lucide-react';
 import { useEffect, useState } from 'react';
-import { cn } from '../../lib/classname';
 import { isLoggedIn } from '../../lib/jwt';
 import { showLoginPopup } from '../../lib/popup';
 import { FineTuneCourse } from './FineTuneCourse';
@@ -11,6 +10,7 @@ import {
   getLastSessionId,
   storeFineTuneData,
 } from '../../lib/ai';
+import { cn } from '../../lib/classname';
 
 export const difficultyLevels = [
   'beginner',
@@ -94,22 +94,46 @@ export function AICourse(props: AICourseProps) {
             value={keyword}
             onChange={(e) => setKeyword(e.target.value)}
             onKeyDown={handleKeyDown}
-            placeholder="Ask tutor to teach you..."
+            placeholder="e.g. JavaScript Promises, React Hooks, Go Routines etc"
             className="w-full rounded-md border-none bg-transparent px-4 pt-4 pb-8 text-gray-900 focus:outline-hidden max-sm:placeholder:text-base"
             maxLength={50}
           />
 
-          <div className="flex flex-row gap-2 px-4">
-            <div className="flex flex-row gap-2">
-              <DifficultyDropdown value={difficulty} onChange={setDifficulty} />
+          <div className="flex flex-row items-center justify-between gap-2 px-4 pb-4">
+            <div className="flex flex-row items-center gap-2">
+              <div className="flex flex-row gap-2">
+                <DifficultyDropdown
+                  value={difficulty}
+                  onChange={setDifficulty}
+                />
+              </div>
+              <label
+                htmlFor="fine-tune-checkbox"
+                className="flex cursor-pointer flex-row items-center gap-1 rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-700 hover:bg-gray-200 hover:text-gray-700"
+              >
+                <input
+                  type="checkbox"
+                  checked={hasFineTuneData}
+                  onChange={() => setHasFineTuneData(!hasFineTuneData)}
+                  className="mr-1"
+                  id="fine-tune-checkbox"
+                />
+                Explain the course
+              </label>
             </div>
+
             <button
-              type="button"
-              onClick={() => setHasFineTuneData(!hasFineTuneData)}
-              className="flex px-2 py-1 bg-gray-100 rounded-full flex-row items-center gap-1 text-sm text-gray-500 hover:text-gray-700"
+              type="submit"
+              disabled={!keyword.trim()}
+              className={cn(
+                'flex items-center justify-center rounded-full px-4 py-1 text-white transition-colors text-sm',
+                !keyword.trim()
+                  ? 'cursor-not-allowed bg-gray-400'
+                  : 'bg-black hover:bg-gray-800',
+              )}
             >
-              <Settings2Icon size={16} />
-              Settings
+              <WandIcon size={18} className="mr-2" />
+              Generate Course
             </button>
           </div>
 
@@ -123,20 +147,6 @@ export function AICourse(props: AICourseProps) {
             setGoal={setGoal}
             setCustomInstructions={setCustomInstructions}
           />
-
-          <button
-            type="submit"
-            disabled={!keyword.trim()}
-            className={cn(
-              'mt-2 flex items-center justify-center rounded-md px-4 py-2 font-medium text-white transition-colors max-sm:text-sm',
-              !keyword.trim()
-                ? 'cursor-not-allowed bg-gray-400'
-                : 'bg-black hover:bg-gray-800',
-            )}
-          >
-            <WandIcon size={18} className="mr-2" />
-            Generate Course
-          </button>
         </form>
       </div>
     </div>
diff --git a/src/components/GenerateCourse/FineTuneCourse.tsx b/src/components/GenerateCourse/FineTuneCourse.tsx
index 0d6cef085..e727c8dfb 100644
--- a/src/components/GenerateCourse/FineTuneCourse.tsx
+++ b/src/components/GenerateCourse/FineTuneCourse.tsx
@@ -1,5 +1,3 @@
-import { cn } from '../../lib/classname';
-
 type QuestionProps = {
   label: string;
   placeholder: string;
@@ -51,52 +49,31 @@ export function FineTuneCourse(props: FineTuneCourseProps) {
     setHasFineTuneData,
   } = props;
 
-  return (
-    <div className="flex flex-col overflow-hidden transition-all">
-      <label
-        className={cn(
-          'group flex cursor-pointer select-none flex-row items-center gap-2.5 px-4 py-3 text-left text-gray-500 transition-colors hover:bg-gray-100 focus:outline-hidden',
-          hasFineTuneData && 'bg-gray-100',
-        )}
-      >
-        <input
-          id="fine-tune-checkbox"
-          type="checkbox"
-          className="h-4 w-4 group-hover:fill-current"
-          checked={hasFineTuneData}
-          onChange={() => {
-            setHasFineTuneData(!hasFineTuneData);
-          }}
-        />
-        Tell us more to tailor the course (optional){' '}
-        <span className="ml-auto rounded-md bg-gray-400 px-2 py-0.5 text-xs text-white hidden sm:block">
-          recommended
-        </span>
-      </label>
+  if (!hasFineTuneData) {
+    return null;
+  }
 
-      {hasFineTuneData && (
-        <div className="mt-0 flex flex-col">
-          <Question
-            label="Tell us about your self"
-            placeholder="e.g. I am a frontend developer and have good knowledge of HTML, CSS, and JavaScript."
-            value={about}
-            onChange={setAbout}
-            autoFocus={true}
-          />
-          <Question
-            label="What is your goal with this course?"
-            placeholder="e.g. I want to be able to build Node.js APIs with Express.js and MongoDB."
-            value={goal}
-            onChange={setGoal}
-          />
-          <Question
-            label="Custom Instructions (Optional)"
-            placeholder="Give additional instructions to the AI as if you were giving them to a friend."
-            value={customInstructions}
-            onChange={setCustomInstructions}
-          />
-        </div>
-      )}
+  return (
+    <div className="mt-0 flex flex-col">
+      <Question
+        label="Tell us about your self"
+        placeholder="e.g. I am a frontend developer and have good knowledge of HTML, CSS, and JavaScript."
+        value={about}
+        onChange={setAbout}
+        autoFocus={true}
+      />
+      <Question
+        label="What is your goal with this course?"
+        placeholder="e.g. I want to be able to build Node.js APIs with Express.js and MongoDB."
+        value={goal}
+        onChange={setGoal}
+      />
+      <Question
+        label="Custom Instructions (Optional)"
+        placeholder="Give additional instructions to the AI as if you were giving them to a friend."
+        value={customInstructions}
+        onChange={setCustomInstructions}
+      />
     </div>
   );
 }

From b3ff46ea712f8ed5bffabe1a1eebc8e185fcf235 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Wed, 9 Apr 2025 19:11:48 +0100
Subject: [PATCH 15/31] Update course

---
 src/components/GenerateCourse/AICourse.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index 5f01d6733..d3d860f9c 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -73,7 +73,7 @@ export function AICourse(props: AICourseProps) {
 
   return (
     <div className="mx-auto flex w-full max-w-3xl flex-grow flex-col justify-center">
-      <h1 className="mb-2.5 text-center text-4xl font-bold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
+      <h1 className="mb-2.5 text-center text-4xl font-semibold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
         What can I help you learn?
       </h1>
       <p className="mb-6 text-center text-lg text-gray-600 max-sm:hidden max-sm:text-left max-sm:text-sm">
@@ -118,7 +118,7 @@ export function AICourse(props: AICourseProps) {
                   className="mr-1"
                   id="fine-tune-checkbox"
                 />
-                Explain the course
+                Explain more for better course
               </label>
             </div>
 

From 61f5a81d20d17130600e1088ae3c32fb521febd1 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Thu, 10 Apr 2025 15:19:50 +0100
Subject: [PATCH 16/31] Tutor sidebar changes

---
 src/components/AITutor/AIFeaturedCoursesListing.tsx |  6 ++----
 src/components/AITutor/AITutorSidebar.tsx           | 13 ++++++++-----
 src/components/GenerateCourse/AICourse.tsx          |  2 +-
 src/pages/ai/{explore.astro => community.astro}     |  2 +-
 4 files changed, 12 insertions(+), 11 deletions(-)
 rename src/pages/ai/{explore.astro => community.astro} (93%)

diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
index a5df649ed..72520266f 100644
--- a/src/components/AITutor/AIFeaturedCoursesListing.tsx
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -1,8 +1,6 @@
 import { useQuery } from '@tanstack/react-query';
 import {
-  listFeaturedAiCoursesOptions,
-  listUserAiCoursesOptions,
-  type ListUserAiCoursesQuery,
+  listFeaturedAiCoursesOptions, type ListUserAiCoursesQuery
 } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
 import { useEffect, useState } from 'react';
@@ -53,7 +51,7 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
     <>
       <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
         <div className="flex items-center gap-2">
-          <h2 className="text-lg font-semibold">Stuff Picks</h2>
+          <h2 className="text-lg font-semibold">Staff Picks</h2>
         </div>
       </div>
 
diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index c4166a676..c6b1a7aa5 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,4 +1,4 @@
-import { BookOpen, Compass, Plus, Star } from 'lucide-react';
+import { BookOpen, Compass, Plus, Star, Users2 } from 'lucide-react';
 import { AITutorLogo } from '../ReactIcons/AITutorLogo';
 
 type AITutorSidebarProps = {
@@ -25,9 +25,9 @@ const sidebarItems = [
     icon: Star,
   },
   {
-    key: 'explore',
-    label: 'Explore',
-    href: '/ai/explore',
+    key: 'community',
+    label: 'Community',
+    href: '/ai/community',
     icon: Compass,
   },
 ];
@@ -48,7 +48,10 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
             AI Tutor
           </h2>
           <span className="text-xs text-gray-500">
-            by <a href="/" className="hover:underline underline-offset-2">roadmap.sh</a>
+            by{' '}
+            <a href="/" className="underline-offset-2 hover:underline">
+              roadmap.sh
+            </a>
           </span>
         </div>
         <p className="max-w-[150px] text-xs text-gray-500">
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index d3d860f9c..d100a7d97 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -118,7 +118,7 @@ export function AICourse(props: AICourseProps) {
                   className="mr-1"
                   id="fine-tune-checkbox"
                 />
-                Explain more for better course
+                Explain more for a better course
               </label>
             </div>
 
diff --git a/src/pages/ai/explore.astro b/src/pages/ai/community.astro
similarity index 93%
rename from src/pages/ai/explore.astro
rename to src/pages/ai/community.astro
index b743e98af..008a46e1d 100644
--- a/src/pages/ai/explore.astro
+++ b/src/pages/ai/community.astro
@@ -11,7 +11,7 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   ogImageUrl={ogImage}
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
-  <AITutorLayout activeTab='explore' client:load>
+  <AITutorLayout activeTab='community' client:load>
     <section class='flex grow flex-col bg-gray-100'>
       <div class='mx-auto w-full flex max-w-4xl flex-col py-10 max-sm:py-4'>
         <AIExploreCourseListing client:load />

From 4fbea4680c14f1955d4ee68809af52962fd10020 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Thu, 10 Apr 2025 15:34:32 +0100
Subject: [PATCH 17/31] Refactor staff picks and community

---
 .../AITutor/AIFeaturedCoursesListing.tsx      | 66 ++++++++-----
 src/components/AITutor/AITutorHeader.tsx      | 46 +++++++++
 src/components/AITutor/AITutorLimits.tsx      | 45 +++++++++
 .../GenerateCourse/UserCoursesList.tsx        | 96 ++++++-------------
 src/pages/ai/staff-picks.astro                |  6 +-
 5 files changed, 160 insertions(+), 99 deletions(-)
 create mode 100644 src/components/AITutor/AITutorHeader.tsx
 create mode 100644 src/components/AITutor/AITutorLimits.tsx

diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
index 72520266f..8799ce06d 100644
--- a/src/components/AITutor/AIFeaturedCoursesListing.tsx
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -4,10 +4,13 @@ import {
 } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
 import { useEffect, useState } from 'react';
-import { Loader2 } from 'lucide-react';
 import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
 import { Pagination } from '../Pagination/Pagination';
+import { AILoadingState } from './AILoadingState';
+import { AITutorTallMessage } from './AITutorTallMessage';
+import { BookOpen } from 'lucide-react';
+import { AITutorHeader } from './AITutorHeader';
 
 type AIFeaturedCoursesListingProps = {};
 
@@ -47,34 +50,45 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
     }
   }, [pageState]);
 
-  return (
-    <>
-      <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
-        <div className="flex items-center gap-2">
-          <h2 className="text-lg font-semibold">Staff Picks</h2>
-        </div>
-      </div>
+  if (isInitialLoading || isFeaturedAiCoursesLoading) {
+    return (
+      <AILoadingState
+        title="Loading featured courses"
+        subtitle="This may take a moment..."
+      />
+    );
+  }
 
-      {(isFeaturedAiCoursesLoading || isInitialLoading) && (
-        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
-          <Loader2
-            className="size-4 animate-spin text-gray-400"
-            strokeWidth={2.5}
-          />
-          <p className="text-sm font-medium text-gray-600">Loading...</p>
-        </div>
-      )}
+  if (courses.length === 0) {
+    return (
+      <AITutorTallMessage
+        title="No featured courses"
+        subtitle="There are no featured courses available at the moment."
+        icon={BookOpen}
+        buttonText="Browse all courses"
+        onButtonClick={() => {
+          window.location.href = '/ai';
+        }}
+      />
+    );
+  }
+
+  return (
+    <div className="w-full">
+      <AITutorHeader title="Featured Courses" />
 
       {!isFeaturedAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
-          {courses.map((course) => (
-            <AICourseCard
-              key={course._id}
-              course={course}
-              showActions={false}
-              showProgress={false}
-            />
-          ))}
+          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
+            {courses.map((course) => (
+              <AICourseCard
+                key={course._id}
+                course={course}
+                showActions={false}
+                showProgress={false}
+              />
+            ))}
+          </div>
 
           <Pagination
             totalCount={featuredAiCourses?.totalCount || 0}
@@ -98,6 +112,6 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
             </p>
           </div>
         )}
-    </>
+    </div>
   );
 }
diff --git a/src/components/AITutor/AITutorHeader.tsx b/src/components/AITutor/AITutorHeader.tsx
new file mode 100644
index 000000000..07b447422
--- /dev/null
+++ b/src/components/AITutor/AITutorHeader.tsx
@@ -0,0 +1,46 @@
+import { useQuery } from '@tanstack/react-query';
+import { AITutorLimits } from './AITutorLimits';
+import { getAiCourseLimitOptions } from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+
+type AITutorHeaderProps = {
+  title: string;
+  isPaidUser: boolean;
+  isPaidUserLoading: boolean;
+  setShowUpgradePopup: (show: boolean) => void;
+  children?: React.ReactNode;
+};
+
+export function AITutorHeader(props: AITutorHeaderProps) {
+  const {
+    title,
+    isPaidUser,
+    isPaidUserLoading,
+    setShowUpgradePopup,
+    children,
+  } = props;
+
+  const { data: limits } = useQuery(getAiCourseLimitOptions(), queryClient);
+
+  const { used, limit } = limits ?? { used: 0, limit: 0 };
+
+  return (
+    <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
+      <div className="flex items-center gap-2">
+        <h2 className="text-lg font-semibold">{title}</h2>
+      </div>
+
+      <div className="flex items-center gap-2">
+        <AITutorLimits
+          used={used}
+          limit={limit}
+          isPaidUser={isPaidUser}
+          isPaidUserLoading={isPaidUserLoading}
+          onUpgradeClick={() => setShowUpgradePopup(true)}
+        />
+
+        {children}
+      </div>
+    </div>
+  );
+}
diff --git a/src/components/AITutor/AITutorLimits.tsx b/src/components/AITutor/AITutorLimits.tsx
new file mode 100644
index 000000000..3cc93730e
--- /dev/null
+++ b/src/components/AITutor/AITutorLimits.tsx
@@ -0,0 +1,45 @@
+import { Gift } from 'lucide-react';
+import { cn } from '../../lib/classname';
+
+type AITutorLimitsProps = {
+  used: number;
+  limit: number;
+  isPaidUser: boolean;
+  isPaidUserLoading: boolean;
+  onUpgradeClick: () => void;
+};
+
+export function AITutorLimits(props: AITutorLimitsProps) {
+  const limitUsedPercentage = Math.round((props.used / props.limit) * 100);
+
+  if (props.used <= 0 || props.limit <= 0 || props.isPaidUserLoading) {
+    return null;
+  }
+
+  return (
+    <div
+      className={cn(
+        'pointer-events-none flex items-center gap-2 opacity-0 transition-opacity',
+        {
+          'pointer-events-auto opacity-100': !props.isPaidUser,
+        },
+      )}
+    >
+      <p className="flex items-center text-sm text-yellow-600">
+        <span className="max-md:hidden">
+          {limitUsedPercentage}% of daily limit used{' '}
+        </span>
+        <span className="inline md:hidden">
+          {limitUsedPercentage}% used
+        </span>
+        <button
+          onClick={props.onUpgradeClick}
+          className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pr-2 pl-1.5 text-xs text-white"
+        >
+          <Gift className="size-4" />
+          Upgrade
+        </button>
+      </p>
+    </div>
+  );
+} 
\ No newline at end of file
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index ee56d665a..b39e061e9 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -1,23 +1,22 @@
 import { useQuery } from '@tanstack/react-query';
+import { BookOpen } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
 import {
-  getAiCourseLimitOptions,
   listUserAiCoursesOptions,
   type ListUserAiCoursesQuery,
 } from '../../queries/ai-course';
-import { queryClient } from '../../stores/query-client';
-import { AICourseCard } from './AICourseCard';
-import { useEffect, useState } from 'react';
-import { BookOpen, Gift } from 'lucide-react';
-import { isLoggedIn } from '../../lib/jwt';
-import { showLoginPopup } from '../../lib/popup';
-import { cn } from '../../lib/classname';
 import { useIsPaidUser } from '../../queries/billing';
-import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
-import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
-import { AICourseSearch } from './AICourseSearch';
-import { Pagination } from '../Pagination/Pagination';
+import { queryClient } from '../../stores/query-client';
 import { AILoadingState } from '../AITutor/AILoadingState';
+import { AITutorHeader } from '../AITutor/AITutorHeader';
 import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { Pagination } from '../Pagination/Pagination';
+import { AICourseCard } from './AICourseCard';
+import { AICourseSearch } from './AICourseSearch';
 
 type UserCoursesListProps = {};
 
@@ -31,12 +30,6 @@ export function UserCoursesList(props: UserCoursesListProps) {
     query: '',
   });
 
-  const { data: limits, isLoading: isLimitsLoading } = useQuery(
-    getAiCourseLimitOptions(),
-    queryClient,
-  );
-
-  const { used, limit } = limits ?? { used: 0, limit: 0 };
   const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
 
   const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
@@ -49,8 +42,6 @@ export function UserCoursesList(props: UserCoursesListProps) {
   }, [userAiCourses]);
 
   const courses = userAiCourses?.data ?? [];
-  const isAuthenticated = isLoggedIn();
-  const limitUsedPercentage = Math.round((used / limit) * 100);
 
   useEffect(() => {
     const queryParams = getUrlParams();
@@ -116,55 +107,24 @@ export function UserCoursesList(props: UserCoursesListProps) {
       {showUpgradePopup && (
         <UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
       )}
-      <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
-        <div className="flex items-center gap-2">
-          <h2 className="text-lg font-semibold">
-            <span className="max-md:hidden">Your </span>Courses
-          </h2>
-        </div>
 
-        <div className="flex items-center gap-2">
-          {used > 0 && limit > 0 && !isPaidUserLoading && (
-            <div
-              className={cn(
-                'pointer-events-none flex items-center gap-2 opacity-0 transition-opacity',
-                {
-                  'pointer-events-auto opacity-100': !isPaidUser,
-                },
-              )}
-            >
-              <p className="flex items-center text-sm text-yellow-600">
-                <span className="max-md:hidden">
-                  {limitUsedPercentage}% of daily limit used{' '}
-                </span>
-                <span className="inline md:hidden">
-                  {limitUsedPercentage}% used
-                </span>
-                <button
-                  onClick={() => {
-                    setShowUpgradePopup(true);
-                  }}
-                  className="ml-1.5 flex items-center gap-1 rounded-full bg-yellow-600 py-0.5 pr-2 pl-1.5 text-xs text-white"
-                >
-                  <Gift className="size-4" />
-                  Upgrade
-                </button>
-              </p>
-            </div>
-          )}
-
-          <AICourseSearch
-            value={pageState?.query || ''}
-            onChange={(value) => {
-              setPageState({
-                ...pageState,
-                query: value,
-                currPage: '1',
-              });
-            }}
-          />
-        </div>
-      </div>
+      <AITutorHeader
+        title="Your Courses"
+        isPaidUser={isPaidUser}
+        isPaidUserLoading={isPaidUserLoading}
+        setShowUpgradePopup={setShowUpgradePopup}
+      >
+        <AICourseSearch
+          value={pageState?.query || ''}
+          onChange={(value) => {
+            setPageState({
+              ...pageState,
+              query: value,
+              currPage: '1',
+            });
+          }}
+        />
+      </AITutorHeader>
 
       {!isUserAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
diff --git a/src/pages/ai/staff-picks.astro b/src/pages/ai/staff-picks.astro
index 840680333..8fcae506a 100644
--- a/src/pages/ai/staff-picks.astro
+++ b/src/pages/ai/staff-picks.astro
@@ -12,10 +12,6 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
   <AITutorLayout activeTab='staff-picks' client:load>
-    <section class='flex grow flex-col bg-gray-100'>
-      <div class='container mx-auto flex max-w-3xl flex-col py-10 max-sm:py-4'>
-        <AIFeaturedCoursesListing client:load />
-      </div>
-    </section>
+    <AIFeaturedCoursesListing client:load />
   </AITutorLayout>
 </SkeletonLayout>

From 3918112884e59a5729466a7b1e41127116d00f57 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Thu, 10 Apr 2025 16:39:01 +0100
Subject: [PATCH 18/31] Update UI for a course

---
 .../AITutor/AIExploreCourseListing.tsx        |  4 +---
 .../AITutor/AIFeaturedCoursesListing.tsx      | 20 +++++++++++++------
 src/components/AITutor/AITutorHeader.tsx      | 16 +++++----------
 .../GenerateCourse/UserCoursesList.tsx        | 11 ++--------
 src/pages/ai/community.astro                  |  6 +-----
 5 files changed, 23 insertions(+), 34 deletions(-)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index 4f132e871..93ea36f46 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -3,9 +3,7 @@ import { useEffect, useState } from 'react';
 import { AlertCircle, Loader2 } from 'lucide-react';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
 
-type AIExploreCourseListingProps = {};
-
-export function AIExploreCourseListing(props: AIExploreCourseListingProps) {
+export function AIExploreCourseListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
 
   const {
diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
index 8799ce06d..2b7fa3468 100644
--- a/src/components/AITutor/AIFeaturedCoursesListing.tsx
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -1,6 +1,7 @@
 import { useQuery } from '@tanstack/react-query';
 import {
-  listFeaturedAiCoursesOptions, type ListUserAiCoursesQuery
+  listFeaturedAiCoursesOptions,
+  type ListUserAiCoursesQuery,
 } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
 import { useEffect, useState } from 'react';
@@ -11,11 +12,11 @@ import { AILoadingState } from './AILoadingState';
 import { AITutorTallMessage } from './AITutorTallMessage';
 import { BookOpen } from 'lucide-react';
 import { AITutorHeader } from './AITutorHeader';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 
-type AIFeaturedCoursesListingProps = {};
-
-export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
+export function AIFeaturedCoursesListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
+  const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
   const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
     perPage: '20',
@@ -75,11 +76,18 @@ export function AIFeaturedCoursesListing(props: AIFeaturedCoursesListingProps) {
 
   return (
     <div className="w-full">
-      <AITutorHeader title="Featured Courses" />
+      {showUpgradePopup && (
+        <UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
+      )}
+
+      <AITutorHeader
+        title="Featured Courses"
+        onUpgradeClick={() => setShowUpgradePopup(true)}
+      />
 
       {!isFeaturedAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
-          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2">
+          <div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
             {courses.map((course) => (
               <AICourseCard
                 key={course._id}
diff --git a/src/components/AITutor/AITutorHeader.tsx b/src/components/AITutor/AITutorHeader.tsx
index 07b447422..77d7c881b 100644
--- a/src/components/AITutor/AITutorHeader.tsx
+++ b/src/components/AITutor/AITutorHeader.tsx
@@ -2,25 +2,19 @@ import { useQuery } from '@tanstack/react-query';
 import { AITutorLimits } from './AITutorLimits';
 import { getAiCourseLimitOptions } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
+import { useIsPaidUser } from '../../queries/billing';
 
 type AITutorHeaderProps = {
   title: string;
-  isPaidUser: boolean;
-  isPaidUserLoading: boolean;
-  setShowUpgradePopup: (show: boolean) => void;
+  onUpgradeClick: () => void;
   children?: React.ReactNode;
 };
 
 export function AITutorHeader(props: AITutorHeaderProps) {
-  const {
-    title,
-    isPaidUser,
-    isPaidUserLoading,
-    setShowUpgradePopup,
-    children,
-  } = props;
+  const { title, onUpgradeClick, children } = props;
 
   const { data: limits } = useQuery(getAiCourseLimitOptions(), queryClient);
+  const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
 
   const { used, limit } = limits ?? { used: 0, limit: 0 };
 
@@ -36,7 +30,7 @@ export function AITutorHeader(props: AITutorHeaderProps) {
           limit={limit}
           isPaidUser={isPaidUser}
           isPaidUserLoading={isPaidUserLoading}
-          onUpgradeClick={() => setShowUpgradePopup(true)}
+          onUpgradeClick={onUpgradeClick}
         />
 
         {children}
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index b39e061e9..943bead6f 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -8,7 +8,6 @@ import {
   listUserAiCoursesOptions,
   type ListUserAiCoursesQuery,
 } from '../../queries/ai-course';
-import { useIsPaidUser } from '../../queries/billing';
 import { queryClient } from '../../stores/query-client';
 import { AILoadingState } from '../AITutor/AILoadingState';
 import { AITutorHeader } from '../AITutor/AITutorHeader';
@@ -18,9 +17,7 @@ import { Pagination } from '../Pagination/Pagination';
 import { AICourseCard } from './AICourseCard';
 import { AICourseSearch } from './AICourseSearch';
 
-type UserCoursesListProps = {};
-
-export function UserCoursesList(props: UserCoursesListProps) {
+export function UserCoursesList() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
   const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
@@ -30,8 +27,6 @@ export function UserCoursesList(props: UserCoursesListProps) {
     query: '',
   });
 
-  const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
-
   const { data: userAiCourses, isFetching: isUserAiCoursesLoading } = useQuery(
     listUserAiCoursesOptions(pageState),
     queryClient,
@@ -110,9 +105,7 @@ export function UserCoursesList(props: UserCoursesListProps) {
 
       <AITutorHeader
         title="Your Courses"
-        isPaidUser={isPaidUser}
-        isPaidUserLoading={isPaidUserLoading}
-        setShowUpgradePopup={setShowUpgradePopup}
+        onUpgradeClick={() => setShowUpgradePopup(true)}
       >
         <AICourseSearch
           value={pageState?.query || ''}
diff --git a/src/pages/ai/community.astro b/src/pages/ai/community.astro
index 008a46e1d..0f9422936 100644
--- a/src/pages/ai/community.astro
+++ b/src/pages/ai/community.astro
@@ -12,10 +12,6 @@ const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
   description='Learn anything with AI Tutor. Pick a topic, choose a difficulty level and the AI will guide you through the learning process.'
 >
   <AITutorLayout activeTab='community' client:load>
-    <section class='flex grow flex-col bg-gray-100'>
-      <div class='mx-auto w-full flex max-w-4xl flex-col py-10 max-sm:py-4'>
-        <AIExploreCourseListing client:load />
-      </div>
-    </section>
+    <AIExploreCourseListing client:load />
   </AITutorLayout>
 </SkeletonLayout>

From ab981b8c88e4ed9ef1da3da7845fd3965f79c9e6 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Thu, 10 Apr 2025 19:45:03 +0100
Subject: [PATCH 19/31] Improve pagination

---
 .../AITutor/AIExploreCourseListing.tsx        | 81 ++++++++++---------
 .../GenerateCourse/UserCoursesList.tsx        |  2 +-
 src/queries/ai-course.ts                      |  2 +-
 3 files changed, 45 insertions(+), 40 deletions(-)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index 93ea36f46..c8b25f560 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -2,9 +2,13 @@ import { useListExploreAiCourses } from '../../queries/ai-course';
 import { useEffect, useState } from 'react';
 import { AlertCircle, Loader2 } from 'lucide-react';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
+import { AILoadingState } from './AILoadingState';
+import { AITutorHeader } from './AITutorHeader';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 
 export function AIExploreCourseListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
+  const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
   const {
     data,
@@ -23,50 +27,51 @@ export function AIExploreCourseListing() {
 
   const courses = data?.pages.flatMap((page) => page.data) ?? [];
 
-  return (
-    <>
-      <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
-        <div className="flex items-center gap-2">
-          <h2 className="text-lg font-semibold">Explore Courses</h2>
-        </div>
+  if (isInitialLoading || isExploreAiCoursesLoading) {
+    return (
+      <AILoadingState
+        title="Loading courses"
+        subtitle="This may take a moment..."
+      />
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
+        <AlertCircle className="size-4 text-red-500" />
+        <p className="text-sm font-medium text-red-600">
+          {error?.message ?? 'Error loading courses.'}
+        </p>
       </div>
+    );
+  }
 
-      {(isExploreAiCoursesLoading || isInitialLoading) && (
-        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
-          <Loader2
-            className="size-4 animate-spin text-gray-400"
-            strokeWidth={2.5}
-          />
-          <p className="text-sm font-medium text-gray-600">Loading...</p>
-        </div>
+  return (
+    <>
+      {showUpgradePopup && (
+        <UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
       )}
 
-      {error && !isExploreAiCoursesLoading && !isInitialLoading && (
-        <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
-          <AlertCircle className="size-4 text-red-500" />
-          <p className="text-sm font-medium text-red-600">
-            {error?.message ?? 'Error loading courses.'}
-          </p>
+      <AITutorHeader
+        title="Explore Courses"
+        onUpgradeClick={() => setShowUpgradePopup(true)}
+      />
+
+      {courses && courses.length > 0 && (
+        <div className="grid grid-cols-3 gap-2">
+          {courses.map((course) => (
+            <AICourseCard
+              key={course._id}
+              course={course}
+              showActions={false}
+              showProgress={false}
+            />
+          ))}
         </div>
       )}
 
-      {!isExploreAiCoursesLoading &&
-        courses &&
-        courses.length > 0 &&
-        !error && (
-          <div className="grid grid-cols-2 gap-2">
-            {courses.map((course) => (
-              <AICourseCard
-                key={course._id}
-                course={course}
-                showActions={false}
-                showProgress={false}
-              />
-            ))}
-          </div>
-        )}
-
-      {hasNextPage && !isFetchingNextPage && !error && (
+      {hasNextPage && !isFetchingNextPage && (
         <div className="mt-4 flex items-center justify-center">
           <button
             onClick={() => fetchNextPage()}
@@ -78,7 +83,7 @@ export function AIExploreCourseListing() {
         </div>
       )}
 
-      {isFetchingNextPage && !error && (
+      {isFetchingNextPage && (
         <div className="mt-4 flex items-center justify-center gap-2">
           <Loader2
             className="size-4 animate-spin text-gray-400"
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index 943bead6f..ece352e6d 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -22,7 +22,7 @@ export function UserCoursesList() {
   const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
   const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
-    perPage: '10',
+    perPage: '21',
     currPage: '1',
     query: '',
   });
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index 4f90e2081..8b269124f 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -154,7 +154,7 @@ export function useListExploreAiCourses() {
         return httpGet<ListExploreAiCoursesResponse>(
           `/v1-list-explore-ai-courses`,
           {
-            perPage: '20',
+            perPage: '21',
             currPage: String(pageParam),
           },
         );

From 2868fa3c27c052aee8aa1e043a0607ef8802ca37 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 11:25:08 +0100
Subject: [PATCH 20/31] Implement pagination of ai tutor ai courses

---
 .../AITutor/AIExploreCourseListing.tsx        | 107 ++++++++++--------
 src/queries/ai-course.ts                      |  50 +++-----
 2 files changed, 80 insertions(+), 77 deletions(-)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index c8b25f560..97ddd2f5b 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -1,31 +1,53 @@
-import { useListExploreAiCourses } from '../../queries/ai-course';
+import { useQuery } from '@tanstack/react-query';
 import { useEffect, useState } from 'react';
-import { AlertCircle, Loader2 } from 'lucide-react';
+import { AlertCircle } from 'lucide-react';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
 import { AILoadingState } from './AILoadingState';
 import { AITutorHeader } from './AITutorHeader';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import {
+  listExploreAiCoursesOptions,
+  type ListExploreAiCoursesQuery,
+} from '../../queries/ai-course';
+import { queryClient } from '../../stores/query-client';
+import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
+import { Pagination } from '../Pagination/Pagination';
 
 export function AIExploreCourseListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
   const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
-  const {
-    data,
-    error,
-    fetchNextPage,
-    hasNextPage,
-    isFetching,
-    isFetchingNextPage,
-    status,
-    isLoading: isExploreAiCoursesLoading,
-  } = useListExploreAiCourses();
+  const [pageState, setPageState] = useState<ListExploreAiCoursesQuery>({
+    perPage: '21',
+    currPage: '1',
+  });
+
+  const { data: exploreAiCourses, isFetching: isExploreAiCoursesLoading } =
+    useQuery(listExploreAiCoursesOptions(pageState), queryClient);
 
   useEffect(() => {
     setIsInitialLoading(false);
-  }, [data]);
+  }, [exploreAiCourses]);
+
+  const courses = exploreAiCourses?.data ?? [];
 
-  const courses = data?.pages.flatMap((page) => page.data) ?? [];
+  useEffect(() => {
+    const queryParams = getUrlParams();
+    setPageState({
+      ...pageState,
+      currPage: queryParams?.p || '1',
+    });
+  }, []);
+
+  useEffect(() => {
+    if (pageState?.currPage !== '1') {
+      setUrlParams({
+        p: pageState?.currPage || '1',
+      });
+    } else {
+      deleteUrlParam('p');
+    }
+  }, [pageState]);
 
   if (isInitialLoading || isExploreAiCoursesLoading) {
     return (
@@ -36,12 +58,12 @@ export function AIExploreCourseListing() {
     );
   }
 
-  if (error) {
+  if (!exploreAiCourses?.data) {
     return (
       <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
         <AlertCircle className="size-4 text-red-500" />
         <p className="text-sm font-medium text-red-600">
-          {error?.message ?? 'Error loading courses.'}
+          Error loading courses.
         </p>
       </div>
     );
@@ -59,39 +81,34 @@ export function AIExploreCourseListing() {
       />
 
       {courses && courses.length > 0 && (
-        <div className="grid grid-cols-3 gap-2">
-          {courses.map((course) => (
-            <AICourseCard
-              key={course._id}
-              course={course}
-              showActions={false}
-              showProgress={false}
-            />
-          ))}
-        </div>
-      )}
+        <div className="flex flex-col gap-2">
+          <div className="grid grid-cols-3 gap-2">
+            {courses.map((course) => (
+              <AICourseCard
+                key={course._id}
+                course={course}
+                showActions={false}
+                showProgress={false}
+              />
+            ))}
+          </div>
 
-      {hasNextPage && !isFetchingNextPage && (
-        <div className="mt-4 flex items-center justify-center">
-          <button
-            onClick={() => fetchNextPage()}
-            disabled={isFetchingNextPage}
-            className="rounded-md border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-100 disabled:opacity-50"
-          >
-            Load more
-          </button>
+          <Pagination
+            totalCount={exploreAiCourses?.totalCount || 0}
+            totalPages={exploreAiCourses?.totalPages || 0}
+            currPage={Number(exploreAiCourses?.currPage || 1)}
+            perPage={Number(exploreAiCourses?.perPage || 21)}
+            onPageChange={(page) => {
+              setPageState({ ...pageState, currPage: String(page) });
+            }}
+            className="rounded-lg border border-gray-200 bg-white p-4"
+          />
         </div>
       )}
 
-      {isFetchingNextPage && (
-        <div className="mt-4 flex items-center justify-center gap-2">
-          <Loader2
-            className="size-4 animate-spin text-gray-400"
-            strokeWidth={2.5}
-          />
-          <p className="text-sm font-medium text-gray-600">
-            Loading more courses...
-          </p>
+      {!isExploreAiCoursesLoading && courses.length === 0 && (
+        <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
+          <p className="text-sm text-gray-600">No courses found.</p>
         </div>
       )}
     </>
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index 8b269124f..846bdb63e 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -1,7 +1,6 @@
 import { httpGet } from '../lib/query-http';
 import { isLoggedIn } from '../lib/jwt';
-import { queryOptions, useInfiniteQuery } from '@tanstack/react-query';
-import { queryClient } from '../stores/query-client';
+import { queryOptions } from '@tanstack/react-query';
 
 export interface AICourseProgressDocument {
   _id: string;
@@ -135,45 +134,32 @@ export function listFeaturedAiCoursesOptions(
 
 type ListExploreAiCoursesParams = {};
 
-type ListExploreAiCoursesQuery = {
+export type ListExploreAiCoursesQuery = {
   perPage?: string;
   currPage?: string;
 };
 
 type ListExploreAiCoursesResponse = {
   data: AICourseWithLessonCount[];
+  totalCount: number;
+  totalPages: number;
   currPage: number;
   perPage: number;
 };
 
-export function useListExploreAiCourses() {
-  return useInfiniteQuery(
-    {
-      queryKey: ['explore-ai-courses'],
-      queryFn: ({ pageParam = 1 }) => {
-        return httpGet<ListExploreAiCoursesResponse>(
-          `/v1-list-explore-ai-courses`,
-          {
-            perPage: '21',
-            currPage: String(pageParam),
-          },
-        );
-      },
-      getNextPageParam: (lastPage, pages, lastPageParam) => {
-        if (lastPage?.data?.length === 0) {
-          return undefined;
-        }
-
-        return lastPageParam + 1;
-      },
-      getPreviousPageParam: (firstPage, allPages, firstPageParam) => {
-        if (firstPageParam <= 1) {
-          return undefined;
-        }
-        return firstPageParam - 1;
-      },
-      initialPageParam: 1,
+export function listExploreAiCoursesOptions(
+  params: ListExploreAiCoursesQuery = {
+    perPage: '21',
+    currPage: '1',
+  },
+) {
+  return {
+    queryKey: ['explore-ai-courses', params],
+    queryFn: () => {
+      return httpGet<ListExploreAiCoursesResponse>(
+        `/v1-list-explore-ai-courses`,
+        params,
+      );
     },
-    queryClient,
-  );
+  };
 }

From b28eb5fecfa0236b5f6ba0c1136e3666ac8847c9 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 12:50:01 +0100
Subject: [PATCH 21/31] AI explore page with search

---
 .../AITutor/AIExploreCourseListing.tsx        | 72 +++++++++++--------
 src/components/AITutor/AILoadingState.tsx     |  2 +-
 src/components/AITutor/AITutorTallMessage.tsx |  2 +-
 src/queries/ai-course.ts                      |  6 +-
 4 files changed, 48 insertions(+), 34 deletions(-)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index 97ddd2f5b..8fbcc366f 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -1,6 +1,5 @@
 import { useQuery } from '@tanstack/react-query';
 import { useEffect, useState } from 'react';
-import { AlertCircle } from 'lucide-react';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
 import { AILoadingState } from './AILoadingState';
 import { AITutorHeader } from './AITutorHeader';
@@ -12,6 +11,9 @@ import {
 import { queryClient } from '../../stores/query-client';
 import { deleteUrlParam, getUrlParams, setUrlParams } from '../../lib/browser';
 import { Pagination } from '../Pagination/Pagination';
+import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
+import { AITutorTallMessage } from './AITutorTallMessage';
+import { BookOpen } from 'lucide-react';
 
 export function AIExploreCourseListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -20,10 +22,14 @@ export function AIExploreCourseListing() {
   const [pageState, setPageState] = useState<ListExploreAiCoursesQuery>({
     perPage: '21',
     currPage: '1',
+    query: '',
   });
 
-  const { data: exploreAiCourses, isFetching: isExploreAiCoursesLoading } =
-    useQuery(listExploreAiCoursesOptions(pageState), queryClient);
+  const {
+    data: exploreAiCourses,
+    isFetching: isExploreAiCoursesLoading,
+    isRefetching: isExploreAiCoursesRefetching,
+  } = useQuery(listExploreAiCoursesOptions(pageState), queryClient);
 
   useEffect(() => {
     setIsInitialLoading(false);
@@ -49,26 +55,6 @@ export function AIExploreCourseListing() {
     }
   }, [pageState]);
 
-  if (isInitialLoading || isExploreAiCoursesLoading) {
-    return (
-      <AILoadingState
-        title="Loading courses"
-        subtitle="This may take a moment..."
-      />
-    );
-  }
-
-  if (!exploreAiCourses?.data) {
-    return (
-      <div className="flex min-h-[152px] items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white py-4">
-        <AlertCircle className="size-4 text-red-500" />
-        <p className="text-sm font-medium text-red-600">
-          Error loading courses.
-        </p>
-      </div>
-    );
-  }
-
   return (
     <>
       {showUpgradePopup && (
@@ -78,9 +64,27 @@ export function AIExploreCourseListing() {
       <AITutorHeader
         title="Explore Courses"
         onUpgradeClick={() => setShowUpgradePopup(true)}
-      />
+      >
+        <AICourseSearch
+          value={pageState?.query || ''}
+          onChange={(value) => {
+            setPageState({
+              ...pageState,
+              query: value,
+              currPage: '1',
+            });
+          }}
+        />
+      </AITutorHeader>
+
+      {(isInitialLoading || isExploreAiCoursesLoading) && (
+        <AILoadingState
+          title="Loading courses"
+          subtitle="This may take a moment..."
+        />
+      )}
 
-      {courses && courses.length > 0 && (
+      {!isExploreAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
           <div className="grid grid-cols-3 gap-2">
             {courses.map((course) => (
@@ -106,11 +110,19 @@ export function AIExploreCourseListing() {
         </div>
       )}
 
-      {!isExploreAiCoursesLoading && courses.length === 0 && (
-        <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
-          <p className="text-sm text-gray-600">No courses found.</p>
-        </div>
-      )}
+      {!isInitialLoading &&
+        !isExploreAiCoursesLoading &&
+        courses.length === 0 && (
+          <AITutorTallMessage
+            title="No courses found"
+            subtitle="Try a different search or check back later."
+            icon={BookOpen}
+            buttonText="Create your first course"
+            onButtonClick={() => {
+              window.location.href = '/ai';
+            }}
+          />
+        )}
     </>
   );
 }
diff --git a/src/components/AITutor/AILoadingState.tsx b/src/components/AITutor/AILoadingState.tsx
index 66ed9de4e..cdaad1020 100644
--- a/src/components/AITutor/AILoadingState.tsx
+++ b/src/components/AITutor/AILoadingState.tsx
@@ -9,7 +9,7 @@ export function AILoadingState(props: AILoadingStateProps) {
   const { title, subtitle } = props;
 
   return (
-    <div className="flex min-h-full w-full flex-col items-center justify-center gap-4 rounded-lg border border-gray-200 bg-white p-8">
+    <div className="flex flex-grow w-full flex-col items-center justify-center gap-4 rounded-lg border border-gray-200 bg-white p-8">
       <div className="relative">
         <Loader2 className="size-12 animate-spin text-gray-300" />
         <div className="absolute inset-0 flex items-center justify-center">
diff --git a/src/components/AITutor/AITutorTallMessage.tsx b/src/components/AITutor/AITutorTallMessage.tsx
index a0000a3ff..a990fe885 100644
--- a/src/components/AITutor/AITutorTallMessage.tsx
+++ b/src/components/AITutor/AITutorTallMessage.tsx
@@ -12,7 +12,7 @@ export function AITutorTallMessage(props: AITutorTallMessageProps) {
   const { title, subtitle, icon: Icon, buttonText, onButtonClick } = props;
 
   return (
-    <div className="flex min-h-full flex-grow flex-col items-center justify-center rounded-lg">
+    <div className="flex flex-grow flex-col items-center justify-center rounded-lg border border-gray-200 bg-white p-8">
       <Icon className="size-12 text-gray-300" />
       <div className="my-4 text-center">
         <h2 className="mb-2 text-xl font-semibold">{title}</h2>
diff --git a/src/queries/ai-course.ts b/src/queries/ai-course.ts
index 846bdb63e..04ffa942c 100644
--- a/src/queries/ai-course.ts
+++ b/src/queries/ai-course.ts
@@ -83,7 +83,7 @@ type ListUserAiCoursesResponse = {
 
 export function listUserAiCoursesOptions(
   params: ListUserAiCoursesQuery = {
-    perPage: '10',
+    perPage: '21',
     currPage: '1',
     query: '',
   },
@@ -117,7 +117,7 @@ type ListFeaturedAiCoursesResponse = {
 
 export function listFeaturedAiCoursesOptions(
   params: ListFeaturedAiCoursesQuery = {
-    perPage: '10',
+    perPage: '21',
     currPage: '1',
   },
 ) {
@@ -137,6 +137,7 @@ type ListExploreAiCoursesParams = {};
 export type ListExploreAiCoursesQuery = {
   perPage?: string;
   currPage?: string;
+  query?: string;
 };
 
 type ListExploreAiCoursesResponse = {
@@ -151,6 +152,7 @@ export function listExploreAiCoursesOptions(
   params: ListExploreAiCoursesQuery = {
     perPage: '21',
     currPage: '1',
+    query: '',
   },
 ) {
   return {

From 1970e0c92ee2cdb12a4bda3ba16548fa26d0fa77 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 17:06:58 +0100
Subject: [PATCH 22/31] Fix pagination of tutor

---
 .../AITutor/AIFeaturedCoursesListing.tsx      | 108 ++++++++----------
 src/components/GenerateCourse/AICourse.tsx    |   3 +-
 .../GenerateCourse/UserCoursesList.tsx        |  56 ++++-----
 3 files changed, 72 insertions(+), 95 deletions(-)

diff --git a/src/components/AITutor/AIFeaturedCoursesListing.tsx b/src/components/AITutor/AIFeaturedCoursesListing.tsx
index 2b7fa3468..0023cda29 100644
--- a/src/components/AITutor/AIFeaturedCoursesListing.tsx
+++ b/src/components/AITutor/AIFeaturedCoursesListing.tsx
@@ -8,18 +8,18 @@ import { useEffect, useState } from 'react';
 import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
 import { AICourseCard } from '../GenerateCourse/AICourseCard';
 import { Pagination } from '../Pagination/Pagination';
-import { AILoadingState } from './AILoadingState';
-import { AITutorTallMessage } from './AITutorTallMessage';
-import { BookOpen } from 'lucide-react';
 import { AITutorHeader } from './AITutorHeader';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { AITutorTallMessage } from './AITutorTallMessage';
+import { BookOpen } from 'lucide-react';
+import { AILoadingState } from './AILoadingState';
 
 export function AIFeaturedCoursesListing() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
   const [showUpgradePopup, setShowUpgradePopup] = useState(false);
 
   const [pageState, setPageState] = useState<ListUserAiCoursesQuery>({
-    perPage: '20',
+    perPage: '21',
     currPage: '1',
   });
 
@@ -51,31 +51,8 @@ export function AIFeaturedCoursesListing() {
     }
   }, [pageState]);
 
-  if (isInitialLoading || isFeaturedAiCoursesLoading) {
-    return (
-      <AILoadingState
-        title="Loading featured courses"
-        subtitle="This may take a moment..."
-      />
-    );
-  }
-
-  if (courses.length === 0) {
-    return (
-      <AITutorTallMessage
-        title="No featured courses"
-        subtitle="There are no featured courses available at the moment."
-        icon={BookOpen}
-        buttonText="Browse all courses"
-        onButtonClick={() => {
-          window.location.href = '/ai';
-        }}
-      />
-    );
-  }
-
   return (
-    <div className="w-full">
+    <>
       {showUpgradePopup && (
         <UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
       )}
@@ -85,41 +62,54 @@ export function AIFeaturedCoursesListing() {
         onUpgradeClick={() => setShowUpgradePopup(true)}
       />
 
-      {!isFeaturedAiCoursesLoading && courses && courses.length > 0 && (
-        <div className="flex flex-col gap-2">
-          <div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
-            {courses.map((course) => (
-              <AICourseCard
-                key={course._id}
-                course={course}
-                showActions={false}
-                showProgress={false}
-              />
-            ))}
-          </div>
-
-          <Pagination
-            totalCount={featuredAiCourses?.totalCount || 0}
-            totalPages={featuredAiCourses?.totalPages || 0}
-            currPage={Number(featuredAiCourses?.currPage || 1)}
-            perPage={Number(featuredAiCourses?.perPage || 10)}
-            onPageChange={(page) => {
-              setPageState({ ...pageState, currPage: String(page) });
-            }}
-            className="rounded-lg border border-gray-200 bg-white p-4"
-          />
-        </div>
+      {(isFeaturedAiCoursesLoading || isInitialLoading) && (
+        <AILoadingState
+          title="Loading featured courses"
+          subtitle="This may take a moment..."
+        />
       )}
 
       {!isFeaturedAiCoursesLoading &&
-        (featuredAiCourses?.data?.length || 0 > 0) &&
-        courses.length === 0 && (
-          <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
-            <p className="text-sm text-gray-600">
-              No courses match your search.
-            </p>
+        !isInitialLoading &&
+        courses.length > 0 && (
+          <div className="flex flex-col gap-2">
+            <div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
+              {courses.map((course) => (
+                <AICourseCard
+                  key={course._id}
+                  course={course}
+                  showActions={false}
+                  showProgress={false}
+                />
+              ))}
+            </div>
+
+            <Pagination
+              totalCount={featuredAiCourses?.totalCount || 0}
+              totalPages={featuredAiCourses?.totalPages || 0}
+              currPage={Number(featuredAiCourses?.currPage || 1)}
+              perPage={Number(featuredAiCourses?.perPage || 10)}
+              onPageChange={(page) => {
+                setPageState({ ...pageState, currPage: String(page) });
+              }}
+              className="rounded-lg border border-gray-200 bg-white p-4"
+            />
           </div>
         )}
-    </div>
+
+      {!isFeaturedAiCoursesLoading &&
+        !isInitialLoading &&
+        courses.length === 0 && (
+          <AITutorTallMessage
+            title="No featured courses"
+            subtitle="There are no featured courses available at the moment."
+            icon={BookOpen}
+            buttonText="Browse all courses"
+            onButtonClick={() => {
+              window.location.href = '/ai';
+            }}
+          />
+        )}
+    </>
   );
 }
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index d100a7d97..eba3cecd5 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -92,6 +92,7 @@ export function AICourse(props: AICourseProps) {
             id="keyword"
             type="text"
             value={keyword}
+            autoFocus={true}
             onChange={(e) => setKeyword(e.target.value)}
             onKeyDown={handleKeyDown}
             placeholder="e.g. JavaScript Promises, React Hooks, Go Routines etc"
@@ -126,7 +127,7 @@ export function AICourse(props: AICourseProps) {
               type="submit"
               disabled={!keyword.trim()}
               className={cn(
-                'flex items-center justify-center rounded-full px-4 py-1 text-white transition-colors text-sm',
+                'flex items-center justify-center rounded-full px-4 py-1 text-sm text-white transition-colors',
                 !keyword.trim()
                   ? 'cursor-not-allowed bg-gray-400'
                   : 'bg-black hover:bg-gray-800',
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index ece352e6d..bef5f21cb 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -9,13 +9,13 @@ import {
   type ListUserAiCoursesQuery,
 } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
-import { AILoadingState } from '../AITutor/AILoadingState';
 import { AITutorHeader } from '../AITutor/AITutorHeader';
 import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 import { Pagination } from '../Pagination/Pagination';
 import { AICourseCard } from './AICourseCard';
 import { AICourseSearch } from './AICourseSearch';
+import { AILoadingState } from '../AITutor/AILoadingState';
 
 export function UserCoursesList() {
   const [isInitialLoading, setIsInitialLoading] = useState(true);
@@ -60,16 +60,7 @@ export function UserCoursesList() {
     }
   }, [pageState]);
 
-  if (isInitialLoading || isUserAiCoursesLoading) {
-    return (
-      <AILoadingState
-        title="Loading your courses"
-        subtitle="This may take a moment..."
-      />
-    );
-  }
-
-  if (!isLoggedIn()) {
+  if (!isInitialLoading && !isLoggedIn()) {
     return (
       <AITutorTallMessage
         title="Sign up or login"
@@ -83,20 +74,6 @@ export function UserCoursesList() {
     );
   }
 
-  if (courses.length === 0) {
-    return (
-      <AITutorTallMessage
-        title="No courses found"
-        subtitle="You haven't generated any courses yet."
-        icon={BookOpen}
-        buttonText="Create your first course"
-        onButtonClick={() => {
-          window.location.href = '/ai';
-        }}
-      />
-    );
-  }
-
   return (
     <>
       {showUpgradePopup && (
@@ -119,7 +96,14 @@ export function UserCoursesList() {
         />
       </AITutorHeader>
 
-      {!isUserAiCoursesLoading && courses && courses.length > 0 && (
+      {(isUserAiCoursesLoading || isInitialLoading) && (
+        <AILoadingState
+          title="Loading your courses"
+          subtitle="This may take a moment..."
+        />
+      )}
+
+      {!isUserAiCoursesLoading && !isInitialLoading && courses.length > 0 && (
         <div className="flex flex-col gap-2">
           <div className="grid grid-cols-3 gap-2">
             {courses.map((course) => (
@@ -140,15 +124,17 @@ export function UserCoursesList() {
         </div>
       )}
 
-      {!isUserAiCoursesLoading &&
-        (userAiCourses?.data?.length || 0 > 0) &&
-        courses.length === 0 && (
-          <div className="flex min-h-[114px] items-center justify-center rounded-lg border border-gray-200 bg-white py-4">
-            <p className="text-sm text-gray-600">
-              No courses match your search.
-            </p>
-          </div>
-        )}
+      {!isUserAiCoursesLoading && !isInitialLoading && courses.length === 0 && (
+        <AITutorTallMessage
+          title="No courses found"
+          subtitle="You haven't generated any courses yet."
+          icon={BookOpen}
+          buttonText="Create your first course"
+          onButtonClick={() => {
+            window.location.href = '/ai';
+          }}
+        />
+      )}
     </>
   );
 }

From 618e4c12335b403a30211399e5fc8c4ddd9f9c28 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 17:18:10 +0100
Subject: [PATCH 23/31] Update tutor header design

---
 src/components/AITutor/AITutorHeader.tsx          |  2 +-
 src/components/GenerateCourse/UserCoursesList.tsx | 13 +++++++++++--
 2 files changed, 12 insertions(+), 3 deletions(-)

diff --git a/src/components/AITutor/AITutorHeader.tsx b/src/components/AITutor/AITutorHeader.tsx
index 77d7c881b..154a87865 100644
--- a/src/components/AITutor/AITutorHeader.tsx
+++ b/src/components/AITutor/AITutorHeader.tsx
@@ -21,7 +21,7 @@ export function AITutorHeader(props: AITutorHeaderProps) {
   return (
     <div className="mb-3 flex min-h-[35px] items-center justify-between max-sm:mb-1">
       <div className="flex items-center gap-2">
-        <h2 className="text-lg font-semibold">{title}</h2>
+        <h2 className="relative flex-shrink-0 top-0 lg:top-1 text-lg font-semibold">{title}</h2>
       </div>
 
       <div className="flex items-center gap-2">
diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx
index bef5f21cb..6a64f34ef 100644
--- a/src/components/GenerateCourse/UserCoursesList.tsx
+++ b/src/components/GenerateCourse/UserCoursesList.tsx
@@ -60,7 +60,16 @@ export function UserCoursesList() {
     }
   }, [pageState]);
 
-  if (!isInitialLoading && !isLoggedIn()) {
+  if (isUserAiCoursesLoading || isInitialLoading) {
+    return (
+      <AILoadingState
+        title="Loading your courses"
+        subtitle="This may take a moment..."
+      />
+    );
+  }
+
+  if (!isLoggedIn()) {
     return (
       <AITutorTallMessage
         title="Sign up or login"
@@ -105,7 +114,7 @@ export function UserCoursesList() {
 
       {!isUserAiCoursesLoading && !isInitialLoading && courses.length > 0 && (
         <div className="flex flex-col gap-2">
-          <div className="grid grid-cols-3 gap-2">
+          <div className="grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3">
             {courses.map((course) => (
               <AICourseCard key={course._id} course={course} />
             ))}

From ec458f2fd2b3f1e82a92e9dfc2e86495cb159bd2 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 18:04:47 +0100
Subject: [PATCH 24/31] Responsiveness of AI

---
 .../AITutor/AIExploreCourseListing.tsx        |   2 +-
 src/components/AITutor/AITutorLayout.tsx      |  33 +++++-
 src/components/AITutor/AITutorSidebar.tsx     | 103 +++++++++++-------
 src/components/GenerateCourse/AICourse.tsx    |  27 ++++-
 4 files changed, 112 insertions(+), 53 deletions(-)

diff --git a/src/components/AITutor/AIExploreCourseListing.tsx b/src/components/AITutor/AIExploreCourseListing.tsx
index 8fbcc366f..15ac7c9ef 100644
--- a/src/components/AITutor/AIExploreCourseListing.tsx
+++ b/src/components/AITutor/AIExploreCourseListing.tsx
@@ -86,7 +86,7 @@ export function AIExploreCourseListing() {
 
       {!isExploreAiCoursesLoading && courses && courses.length > 0 && (
         <div className="flex flex-col gap-2">
-          <div className="grid grid-cols-3 gap-2">
+          <div className="grid grid-cols-1 gap-2 md:grid-cols-2 lg:grid-cols-3">
             {courses.map((course) => (
               <AICourseCard
                 key={course._id}
diff --git a/src/components/AITutor/AITutorLayout.tsx b/src/components/AITutor/AITutorLayout.tsx
index c7663c48f..b50849495 100644
--- a/src/components/AITutor/AITutorLayout.tsx
+++ b/src/components/AITutor/AITutorLayout.tsx
@@ -1,4 +1,7 @@
+import { Menu } from 'lucide-react';
+import { useState } from 'react';
 import { AITutorSidebar, type AITutorTab } from './AITutorSidebar';
+import { RoadmapLogoIcon } from '../ReactIcons/RoadmapLogo';
 
 type AITutorLayoutProps = {
   children: React.ReactNode;
@@ -8,12 +11,32 @@ type AITutorLayoutProps = {
 export function AITutorLayout(props: AITutorLayoutProps) {
   const { children, activeTab } = props;
 
+  const [isSidebarFloating, setIsSidebarFloating] = useState(false);
+
   return (
-    <div className="flex flex-grow flex-row">
-      <AITutorSidebar activeTab={activeTab} />
-      <div className="flex flex-grow h-screen overflow-y-scroll flex-col bg-gray-100 px-4 py-4">
-        {children}
+    <>
+      <div className="flex flex-row items-center justify-between border-b border-slate-200 px-4 py-3 lg:hidden">
+        <a href="/" className="flex flex-row items-center gap-1.5">
+          <RoadmapLogoIcon className="size-6 text-gray-500" color="black" />
+        </a>
+        <button
+          className="flex flex-row items-center gap-1"
+          onClick={() => setIsSidebarFloating(!isSidebarFloating)}
+        >
+          <Menu className="size-5 text-gray-500" />
+        </button>
+      </div>
+
+      <div className="flex flex-grow flex-row">
+        <AITutorSidebar
+          onClose={() => setIsSidebarFloating(false)}
+          isFloating={isSidebarFloating}
+          activeTab={activeTab}
+        />
+        <div className="flex flex-grow flex-col overflow-y-scroll bg-gray-100 p-3 lg:px-4 lg:py-4">
+          {children}
+        </div>
       </div>
-    </div>
+    </>
   );
 }
diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index c6b1a7aa5..5ee4fccc2 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,8 +1,10 @@
-import { BookOpen, Compass, Plus, Star, Users2 } from 'lucide-react';
+import { BookOpen, Compass, Plus, Star, X } from 'lucide-react';
 import { AITutorLogo } from '../ReactIcons/AITutorLogo';
 
 type AITutorSidebarProps = {
+  isFloating: boolean;
   activeTab: AITutorTab;
+  onClose: () => void;
 };
 
 const sidebarItems = [
@@ -35,49 +37,68 @@ const sidebarItems = [
 export type AITutorTab = (typeof sidebarItems)[number]['key'];
 
 export function AITutorSidebar(props: AITutorSidebarProps) {
-  const { activeTab } = props;
+  const { activeTab, isFloating, onClose } = props;
 
   return (
-    <aside className="hidden w-[255px] shrink-0 border-r border-slate-200 md:block">
-      <div className="flex flex-col items-start justify-center px-6 py-5">
-        <div className="flex flex-row items-center gap-1">
-          <AITutorLogo className="size-11 text-gray-500" color="black" />
+    <>
+      <aside
+        className={`w-[255px] shrink-0 border-r border-slate-200 ${
+          isFloating
+            ? 'fixed top-0 bottom-0 left-0 z-50 block border-r-0 bg-white shadow-xl'
+            : 'hidden lg:block'
+        }`}
+      >
+        {isFloating && (
+          <button className="absolute top-3 right-3" onClick={onClose}>
+            <X
+              strokeWidth={3}
+              className="size-3.5 text-gray-400 hover:text-black"
+            />
+          </button>
+        )}
+        <div className="flex flex-col items-start justify-center px-6 py-5">
+          <div className="flex flex-row items-center gap-1">
+            <AITutorLogo className="size-11 text-gray-500" color="black" />
+          </div>
+          <div className="my-3 flex flex-col">
+            <h2 className="-mb-px text-base font-semibold text-black">
+              AI Tutor
+            </h2>
+            <span className="text-xs text-gray-500">
+              by{' '}
+              <a href="/" className="underline-offset-2 hover:underline">
+                roadmap.sh
+              </a>
+            </span>
+          </div>
+          <p className="max-w-[150px] text-xs text-gray-500">
+            Your personalized learning companion for any topic
+          </p>
         </div>
-        <div className="my-3 flex flex-col">
-          <h2 className="-mb-px text-base font-semibold text-black">
-            AI Tutor
-          </h2>
-          <span className="text-xs text-gray-500">
-            by{' '}
-            <a href="/" className="underline-offset-2 hover:underline">
-              roadmap.sh
-            </a>
-          </span>
-        </div>
-        <p className="max-w-[150px] text-xs text-gray-500">
-          Your personalized learning companion for any topic
-        </p>
-      </div>
 
-      <ul className="space-y-1">
-        {sidebarItems.map((item) => (
-          <li key={item.key}>
-            <a
-              href={item.href}
-              className={`font-regular flex w-full items-center border-r-2 px-5 py-2 text-sm transition-all ${
-                activeTab === item.key
-                  ? 'border-r-black bg-gray-100 text-black'
-                  : 'border-r-transparent text-gray-500 hover:border-r-gray-300'
-              }`}
-            >
-              <span className="flex grow items-center">
-                <item.icon className="mr-2 size-4" />
-                {item.label}
-              </span>
-            </a>
-          </li>
-        ))}
-      </ul>
-    </aside>
+        <ul className="space-y-1">
+          {sidebarItems.map((item) => (
+            <li key={item.key}>
+              <a
+                href={item.href}
+                className={`font-regular flex w-full items-center border-r-2 px-5 py-2 text-sm transition-all ${
+                  activeTab === item.key
+                    ? 'border-r-black bg-gray-100 text-black'
+                    : 'border-r-transparent text-gray-500 hover:border-r-gray-300'
+                }`}
+              >
+                <span className="flex grow items-center">
+                  <item.icon className="mr-2 size-4" />
+                  {item.label}
+                </span>
+              </a>
+            </li>
+          ))}
+        </ul>
+      </aside>
+      {isFloating && (
+        <div className="fixed inset-0 z-40 bg-black/50" onClick={onClose} />
+      )}
+    </>
   );
 }
diff --git a/src/components/GenerateCourse/AICourse.tsx b/src/components/GenerateCourse/AICourse.tsx
index eba3cecd5..2e6fffb70 100644
--- a/src/components/GenerateCourse/AICourse.tsx
+++ b/src/components/GenerateCourse/AICourse.tsx
@@ -72,11 +72,11 @@ export function AICourse(props: AICourseProps) {
   }
 
   return (
-    <div className="mx-auto flex w-full max-w-3xl flex-grow flex-col justify-center">
-      <h1 className="mb-2.5 text-center text-4xl font-semibold max-sm:mb-2 max-sm:text-left max-sm:text-xl">
+    <div className="mx-auto flex w-full max-w-3xl flex-grow flex-col pt-4 md:justify-center md:pt-10 lg:pt-0">
+      <h1 className="mb-0.5 text-center text-4xl font-semibold max-md:text-left max-md:text-xl lg:mb-3">
         What can I help you learn?
       </h1>
-      <p className="mb-6 text-center text-lg text-gray-600 max-sm:hidden max-sm:text-left max-sm:text-sm">
+      <p className="mb-3 text-balance text-center text-lg text-gray-600 max-md:text-left max-md:text-sm lg:mb-6">
         Enter a topic below to generate a personalized course for it
       </p>
 
@@ -100,7 +100,7 @@ export function AICourse(props: AICourseProps) {
             maxLength={50}
           />
 
-          <div className="flex flex-row items-center justify-between gap-2 px-4 pb-4">
+          <div className="flex flex-col items-start justify-between gap-2 px-4 pb-4 md:flex-row md:items-center">
             <div className="flex flex-row items-center gap-2">
               <div className="flex flex-row gap-2">
                 <DifficultyDropdown
@@ -119,7 +119,8 @@ export function AICourse(props: AICourseProps) {
                   className="mr-1"
                   id="fine-tune-checkbox"
                 />
-                Explain more for a better course
+                Explain more
+                <span className="hidden md:inline"> for a better course</span>
               </label>
             </div>
 
@@ -127,7 +128,7 @@ export function AICourse(props: AICourseProps) {
               type="submit"
               disabled={!keyword.trim()}
               className={cn(
-                'flex items-center justify-center rounded-full px-4 py-1 text-sm text-white transition-colors',
+                'hidden items-center justify-center rounded-full px-4 py-1 text-sm text-white transition-colors md:flex',
                 !keyword.trim()
                   ? 'cursor-not-allowed bg-gray-400'
                   : 'bg-black hover:bg-gray-800',
@@ -148,6 +149,20 @@ export function AICourse(props: AICourseProps) {
             setGoal={setGoal}
             setCustomInstructions={setCustomInstructions}
           />
+
+          <button
+            type="submit"
+            disabled={!keyword.trim()}
+            className={cn(
+              'mx-4 mb-3 flex items-center justify-center rounded-full px-4 py-1 text-sm text-white transition-colors md:hidden',
+              !keyword.trim()
+                ? 'cursor-not-allowed bg-gray-400'
+                : 'bg-black hover:bg-gray-800',
+            )}
+          >
+            <WandIcon size={18} className="mr-2" />
+            Generate Course
+          </button>
         </form>
       </div>
     </div>

From fd9b388834b3891c5acc6e7205a15b63d9e2b49b Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 18:22:51 +0100
Subject: [PATCH 25/31] Fork alert changes

---
 src/components/GenerateCourse/AICourseContent.tsx | 12 ------------
 src/components/GenerateCourse/ForkCourseAlert.tsx |  4 ++--
 2 files changed, 2 insertions(+), 14 deletions(-)

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index 820b33197..e2abc32dc 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -8,7 +8,6 @@ import {
   Map,
   MessageCircleOffIcon,
   MessageCircleIcon,
-  GitForkIcon,
 } from 'lucide-react';
 import { useEffect, useState } from 'react';
 import { type AiCourse } from '../../lib/ai';
@@ -328,17 +327,6 @@ export function AICourseContent(props: AICourseContentProps) {
               onUpgrade={() => setShowUpgradeModal(true)}
               onShowLimits={() => setShowAILimitsPopup(true)}
             />
-            {isForkable && (
-              <button
-                className="hidden items-center justify-center gap-1 rounded-md bg-yellow-400 px-4 py-1 text-sm font-medium underline-offset-2 hover:bg-yellow-500 lg:flex"
-                onClick={() => {
-                  setIsForkingCourse(true);
-                }}
-              >
-                <GitForkIcon className="size-4" />
-                Fork
-              </button>
-            )}
           </div>
         </div>
       </header>
diff --git a/src/components/GenerateCourse/ForkCourseAlert.tsx b/src/components/GenerateCourse/ForkCourseAlert.tsx
index e7280b94a..7f3f7b0dc 100644
--- a/src/components/GenerateCourse/ForkCourseAlert.tsx
+++ b/src/components/GenerateCourse/ForkCourseAlert.tsx
@@ -17,13 +17,13 @@ export function ForkCourseAlert(props: ForkCourseAlertProps) {
   }
 
   return (
-    <div className="mb-4 flex items-center justify-between gap-2 rounded-lg bg-yellow-200 p-3 text-black">
+    <div className="mb-3.5 lg:-mt-2.5 max-w-5xl mx-auto flex items-center justify-between gap-2 rounded-lg bg-yellow-200 p-3 text-black">
       <p className="text-sm text-balance">
         To start tracking your progress, you can fork the course.
       </p>
 
       <button
-        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 p-1 px-2 text-sm text-black"
+        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 py-1.5 px-3 text-sm text-black"
         onClick={onForkCourse}
       >
         <GitForkIcon className="size-3.5" />

From a75f97360a21b4a4afdd85463b1d1a69134d9636 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 19:00:25 +0100
Subject: [PATCH 26/31] Responsiveness of actions

---
 .../GenerateCourse/AICourseContent.tsx          |  7 +++++--
 .../GenerateCourse/AICourseLesson.tsx           | 17 +++++++++++++++--
 .../GenerateCourse/ForkCourseAlert.tsx          | 14 ++++++++++----
 .../GenerateCourse/RegenerateLesson.tsx         |  2 +-
 4 files changed, 31 insertions(+), 9 deletions(-)

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index e2abc32dc..462404049 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -248,7 +248,10 @@ export function AICourseContent(props: AICourseContentProps) {
             aria-label="Back to generator"
           >
             <ChevronLeft className="size-4" strokeWidth={2.5} />
-            Back {isViewingLesson ? 'to Outline' : 'to AI Tutor'}
+            Back{' '}
+            <span className="hidden lg:inline">
+              {isViewingLesson ? 'to Outline' : 'to AI Tutor'}
+            </span>
           </a>
           <div className="flex items-center gap-2">
             <div className="flex flex-row lg:hidden">
@@ -439,7 +442,6 @@ export function AICourseContent(props: AICourseContentProps) {
             courseSlug &&
             (viewMode === 'outline' || viewMode === 'roadmap') && (
               <ForkCourseAlert
-                courseSlug={courseSlug}
                 creatorId={creatorId}
                 onForkCourse={() => {
                   setIsForkingCourse(true);
@@ -459,6 +461,7 @@ export function AICourseContent(props: AICourseContentProps) {
           {viewMode === 'module' && (
             <AICourseLesson
               courseSlug={courseSlug!}
+              creatorId={creatorId}
               progress={aiCourseProgress}
               activeModuleIndex={activeModuleIndex}
               totalModules={totalModules}
diff --git a/src/components/GenerateCourse/AICourseLesson.tsx b/src/components/GenerateCourse/AICourseLesson.tsx
index 3a10b5680..dd4a443a2 100644
--- a/src/components/GenerateCourse/AICourseLesson.tsx
+++ b/src/components/GenerateCourse/AICourseLesson.tsx
@@ -3,6 +3,7 @@ import {
   CheckIcon,
   ChevronLeft,
   ChevronRight,
+  GitForkIcon,
   Loader2Icon,
   LockIcon,
   MessageCircleIcon,
@@ -55,6 +56,7 @@ function getQuestionsFromResult(result: string) {
 type AICourseLessonProps = {
   courseSlug: string;
   progress: string[];
+  creatorId?: string;
 
   activeModuleIndex: number;
   totalModules: number;
@@ -79,6 +81,7 @@ export function AICourseLesson(props: AICourseLessonProps) {
   const {
     courseSlug,
     progress = [],
+    creatorId,
 
     activeModuleIndex,
     totalModules,
@@ -298,13 +301,13 @@ export function AICourseLesson(props: AICourseLessonProps) {
                 </div>
               )}
 
-              <div className="mb-4 flex items-center justify-between">
+              <div className="mb-4 flex max-sm:flex-col-reverse justify-between">
                 <div className="text-sm text-gray-500">
                   Lesson {activeLessonIndex + 1} of {totalLessons}
                 </div>
 
                 {!isGenerating && !isLoading && (
-                  <div className="absolute top-2 right-2 flex items-center justify-between gap-2 lg:top-6 lg:right-6">
+                  <div className="md:absolute top-2 right-2 flex items-center max-sm:justify-end gap-2 lg:top-6 lg:right-6 mb-3">
                     <button
                       onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
                       className="rounded-full p-1 text-gray-400 hover:text-black max-lg:hidden"
@@ -323,6 +326,16 @@ export function AICourseLesson(props: AICourseLessonProps) {
                       isForkable={isForkable}
                       onForkCourse={onForkCourse}
                     />
+
+                    {isForkable && (
+                      <button
+                        onClick={onForkCourse}
+                        className="flex items-center gap-1.5 rounded-full border bg-gray-100 py-1 pr-4 pl-3 text-sm text-black hover:bg-gray-200 disabled:opacity-50 max-lg:text-xs"
+                      >
+                        <GitForkIcon className="size-3.5" />
+                        Fork Course
+                      </button>
+                    )}
                     <button
                       disabled={isLoading || isTogglingDone}
                       className={cn(
diff --git a/src/components/GenerateCourse/ForkCourseAlert.tsx b/src/components/GenerateCourse/ForkCourseAlert.tsx
index 7f3f7b0dc..01fdfc8c3 100644
--- a/src/components/GenerateCourse/ForkCourseAlert.tsx
+++ b/src/components/GenerateCourse/ForkCourseAlert.tsx
@@ -1,14 +1,15 @@
 import { GitForkIcon } from 'lucide-react';
 import { getUser } from '../../lib/jwt';
+import { cn } from '../../lib/classname';
 
 type ForkCourseAlertProps = {
-  courseSlug: string;
+  className?: string;
   creatorId?: string;
   onForkCourse: () => void;
 };
 
 export function ForkCourseAlert(props: ForkCourseAlertProps) {
-  const { courseSlug, creatorId, onForkCourse } = props;
+  const { creatorId, onForkCourse, className = '' } = props;
 
   const currentUser = getUser();
 
@@ -17,13 +18,18 @@ export function ForkCourseAlert(props: ForkCourseAlertProps) {
   }
 
   return (
-    <div className="mb-3.5 lg:-mt-2.5 max-w-5xl mx-auto flex items-center justify-between gap-2 rounded-lg bg-yellow-200 p-3 text-black">
+    <div
+      className={cn(
+        'mx-auto mb-3.5 flex max-w-5xl items-center justify-between gap-2 rounded-lg bg-yellow-200 p-3 text-black lg:-mt-2.5',
+        className,
+      )}
+    >
       <p className="text-sm text-balance">
         To start tracking your progress, you can fork the course.
       </p>
 
       <button
-        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 py-1.5 px-3 text-sm text-black"
+        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 px-3 py-1.5 text-sm text-black"
         onClick={onForkCourse}
       >
         <GitForkIcon className="size-3.5" />
diff --git a/src/components/GenerateCourse/RegenerateLesson.tsx b/src/components/GenerateCourse/RegenerateLesson.tsx
index 2427c119c..507d106b9 100644
--- a/src/components/GenerateCourse/RegenerateLesson.tsx
+++ b/src/components/GenerateCourse/RegenerateLesson.tsx
@@ -49,7 +49,7 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
         />
       )}
 
-      <div className="relative mr-2 flex items-center" ref={ref}>
+      <div className="relative lg:mr-1 flex items-center" ref={ref}>
         <button
           className={cn('rounded-full p-1 text-gray-400 hover:text-black', {
             'text-black': isDropdownVisible,

From 926a08ac6c60a79ff7ea64c74a05171ae68a8ab2 Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 19:06:34 +0100
Subject: [PATCH 27/31] Forking functionality changes

---
 .../GenerateCourse/ForkCourseConfirmation.tsx  | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/src/components/GenerateCourse/ForkCourseConfirmation.tsx b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
index b0c19f67b..d91a8aee8 100644
--- a/src/components/GenerateCourse/ForkCourseConfirmation.tsx
+++ b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
@@ -48,13 +48,19 @@ export function ForkCourseConfirmation(props: ForkCourseConfirmationProps) {
   );
 
   return (
-    <Modal onClose={isPending ? () => {} : onClose}>
+    <Modal
+      onClose={isPending ? () => {} : onClose}
+      wrapperClassName="h-auto items-start"
+      overlayClassName="items-start md:items-center"
+    >
       <div className="flex flex-col items-center p-4 pt-8">
-        <GitForkIcon className="size-14 text-gray-500" />
-        <p className="mt-2 text-xl font-medium">Fork Course</p>
-        <p className="mt-1 text-center text-balance text-gray-500">
-          Forking this course will create a new course with the same content.
-        </p>
+        <GitForkIcon className="size-14 text-gray-300" />
+        <div className="my-5 text-center">
+          <p className="text-xl font-semibold">Fork Course</p>
+          <p className="mt-1 text-center text-balance text-gray-500">
+            Create a copy of this course
+          </p>
+        </div>
 
         <div className="mt-4 grid w-full grid-cols-2 gap-2">
           <button

From 5a4cdc849f33b4dce379247742a8b211c9b1168e Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 19:24:37 +0100
Subject: [PATCH 28/31] Fork confirmation changes

---
 .../GenerateCourse/ForkCourseAlert.tsx        |  4 +-
 .../GenerateCourse/ForkCourseConfirmation.tsx | 41 +++++++++++--------
 2 files changed, 27 insertions(+), 18 deletions(-)

diff --git a/src/components/GenerateCourse/ForkCourseAlert.tsx b/src/components/GenerateCourse/ForkCourseAlert.tsx
index 01fdfc8c3..b00658246 100644
--- a/src/components/GenerateCourse/ForkCourseAlert.tsx
+++ b/src/components/GenerateCourse/ForkCourseAlert.tsx
@@ -25,11 +25,11 @@ export function ForkCourseAlert(props: ForkCourseAlertProps) {
       )}
     >
       <p className="text-sm text-balance">
-        To start tracking your progress, you can fork the course.
+        Fork the course to track progress and make changes to the course.
       </p>
 
       <button
-        className="flex shrink-0 items-center gap-2 rounded-md bg-yellow-400 px-3 py-1.5 text-sm text-black"
+        className="flex shrink-0 items-center gap-2 rounded-md hover:bg-yellow-500 bg-yellow-400 px-3 py-1.5 text-sm text-black"
         onClick={onForkCourse}
       >
         <GitForkIcon className="size-3.5" />
diff --git a/src/components/GenerateCourse/ForkCourseConfirmation.tsx b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
index d91a8aee8..7073a2a8a 100644
--- a/src/components/GenerateCourse/ForkCourseConfirmation.tsx
+++ b/src/components/GenerateCourse/ForkCourseConfirmation.tsx
@@ -1,4 +1,4 @@
-import { GitForkIcon, Loader2Icon } from 'lucide-react';
+import { Copy, GitForkIcon, Loader2Icon } from 'lucide-react';
 import { Modal } from '../Modal';
 import type { AICourseDocument } from '../../queries/ai-course';
 import { useMutation } from '@tanstack/react-query';
@@ -50,37 +50,46 @@ export function ForkCourseConfirmation(props: ForkCourseConfirmationProps) {
   return (
     <Modal
       onClose={isPending ? () => {} : onClose}
-      wrapperClassName="h-auto items-start"
+      wrapperClassName="h-auto items-start max-w-md w-full"
       overlayClassName="items-start md:items-center"
     >
-      <div className="flex flex-col items-center p-4 pt-8">
-        <GitForkIcon className="size-14 text-gray-300" />
-        <div className="my-5 text-center">
-          <p className="text-xl font-semibold">Fork Course</p>
-          <p className="mt-1 text-center text-balance text-gray-500">
-            Create a copy of this course
+      <div className="relative flex flex-col items-center p-8">
+        <div className="p-4">
+          <Copy className="size-12 text-gray-300" strokeWidth={1.5} />
+        </div>
+
+        <div className="mt-6 text-center">
+          <h2 className="text-2xl font-bold text-gray-900">Fork Course</h2>
+          <p className="mt-3 text-center leading-relaxed text-balance text-gray-600">
+            Create a copy of this course to track your progress and make changes
+            to suit your learning style.
           </p>
         </div>
 
-        <div className="mt-4 grid w-full grid-cols-2 gap-2">
+        <div className="mt-8 grid w-full grid-cols-2 gap-3">
           <button
             disabled={isPending}
-            className="flex items-center justify-center gap-2 rounded-md bg-gray-100 p-2 hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-50"
+            onClick={onClose}
+            className="flex items-center justify-center gap-2 rounded-lg border border-gray-200 px-4 py-2.5 font-medium text-gray-700 transition-all hover:border-gray-300 hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50"
           >
             Cancel
           </button>
 
           <button
             disabled={isPending}
-            className="flex h-10 items-center justify-center gap-2 rounded-md bg-black p-2 text-white hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50"
-            onClick={() => {
-              forkCourse();
-            }}
+            className="flex hover:opacity-80 items-center justify-center gap-2 rounded-lg bg-black px-4 py-2.5 font-medium text-white transition-all hover:bg-gray-900 disabled:cursor-not-allowed disabled:opacity-50"
+            onClick={() => forkCourse()}
           >
             {isPending ? (
-              <Loader2Icon className="size-4 animate-spin" />
+              <>
+                <Loader2Icon className="size-4 animate-spin" />
+                <span>Forking...</span>
+              </>
             ) : (
-              'Fork Course'
+              <>
+                <GitForkIcon className="size-4" />
+                <span>Fork Course</span>
+              </>
             )}
           </button>
         </div>

From 4df9eebb34e9bba343d09f2008f3fb9051c1663f Mon Sep 17 00:00:00 2001
From: Kamran Ahmed <kamranahmed.se@gmail.com>
Date: Fri, 11 Apr 2025 20:20:08 +0100
Subject: [PATCH 29/31] Add upgrade indicator in sidebar

---
 src/components/AITutor/AITutorSidebar.tsx     | 41 ++++++++++++++++++-
 .../AITutor/AITutorSidebarProps.tsx           | 13 ++++++
 .../Billing/UpgradeAccountModal.tsx           |  9 +++-
 3 files changed, 61 insertions(+), 2 deletions(-)
 create mode 100644 src/components/AITutor/AITutorSidebarProps.tsx

diff --git a/src/components/AITutor/AITutorSidebar.tsx b/src/components/AITutor/AITutorSidebar.tsx
index 5ee4fccc2..471d75849 100644
--- a/src/components/AITutor/AITutorSidebar.tsx
+++ b/src/components/AITutor/AITutorSidebar.tsx
@@ -1,5 +1,9 @@
-import { BookOpen, Compass, Plus, Star, X } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { BookOpen, Compass, Plus, Star, X, Zap } from 'lucide-react';
 import { AITutorLogo } from '../ReactIcons/AITutorLogo';
+import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
+import { useIsPaidUser } from '../../queries/billing';
+import { isLoggedIn } from '../../lib/jwt';
 
 type AITutorSidebarProps = {
   isFloating: boolean;
@@ -39,8 +43,21 @@ export type AITutorTab = (typeof sidebarItems)[number]['key'];
 export function AITutorSidebar(props: AITutorSidebarProps) {
   const { activeTab, isFloating, onClose } = props;
 
+  const [isInitialLoad, setIsInitialLoad] = useState(true);
+
+  const [isUpgradeModalOpen, setIsUpgradeModalOpen] = useState(false);
+  const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser();
+
+  useEffect(() => {
+    setIsInitialLoad(false);
+  }, []);
+
   return (
     <>
+      {isUpgradeModalOpen && (
+        <UpgradeAccountModal onClose={() => setIsUpgradeModalOpen(false)} />
+      )}
+
       <aside
         className={`w-[255px] shrink-0 border-r border-slate-200 ${
           isFloating
@@ -94,6 +111,28 @@ export function AITutorSidebar(props: AITutorSidebarProps) {
               </a>
             </li>
           ))}
+
+          {!isInitialLoad &&
+            isLoggedIn() &&
+            !isPaidUser &&
+            !isPaidUserLoading && (
+              <li>
+                <button
+                  onClick={() => {
+                    setIsUpgradeModalOpen(true);
+                  }}
+                  className="mx-4 mt-4 rounded-xl bg-amber-100 p-4 text-left transition-colors hover:bg-amber-200/80"
+                >
+                  <span className="mb-2 flex items-center gap-2">
+                    <Zap className="size-4 text-amber-600" />
+                    <span className="font-medium text-amber-900">Upgrade</span>
+                  </span>
+                  <span className="mt-1 block text-left text-xs leading-4 text-amber-700">
+                    Get access to all features and benefits of the AI Tutor.
+                  </span>
+                </button>
+              </li>
+            )}
         </ul>
       </aside>
       {isFloating && (
diff --git a/src/components/AITutor/AITutorSidebarProps.tsx b/src/components/AITutor/AITutorSidebarProps.tsx
new file mode 100644
index 000000000..8ede3e1a4
--- /dev/null
+++ b/src/components/AITutor/AITutorSidebarProps.tsx
@@ -0,0 +1,13 @@
+import { Zap } from 'lucide-react';
+
+<li>
+  <div className="mx-4 mt-4 rounded-lg bg-amber-50 p-3">
+    <div className="flex items-center gap-2">
+      <Zap className="size-4 text-amber-600" />
+      <span className="font-medium text-amber-900">Free Tier</span>
+    </div>
+    <p className="mt-1 text-xs text-amber-700">
+      Upgrade to Pro to unlock unlimited AI tutoring sessions
+    </p>
+  </div>
+</li> 
\ No newline at end of file
diff --git a/src/components/Billing/UpgradeAccountModal.tsx b/src/components/Billing/UpgradeAccountModal.tsx
index 452fd2665..359c4acab 100644
--- a/src/components/Billing/UpgradeAccountModal.tsx
+++ b/src/components/Billing/UpgradeAccountModal.tsx
@@ -234,7 +234,14 @@ export function UpgradeAccountModal(props: UpgradeAccountModalProps) {
                           )}
                         </p>
                       )}
-                      <p className="text-2xl font-bold text-black sm:text-3xl">
+                      <p
+                        className={cn(
+                          'text-2xl font-bold text-black sm:text-3xl',
+                          {
+                            'mt-0 md:mt-6': !isYearly,
+                          },
+                        )}
+                      >
                         ${plan.amount}{' '}
                         <span className="text-xs font-normal text-gray-500 sm:text-sm">
                           / {isYearly ? 'year' : 'month'}

From 74c20a66fca0bcfc3f8b3d377188e0985fff79a8 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Sat, 12 Apr 2025 01:26:17 +0600
Subject: [PATCH 30/31] fix: ai course access

---
 .../GenerateCourse/AICourseContent.tsx        |  4 +++
 .../GenerateCourse/AICourseLesson.tsx         | 20 +++++++++--
 .../GenerateCourse/AICourseLessonChat.tsx     | 22 ++++++++++--
 .../GenerateCourse/AICourseLimit.tsx          |  5 +++
 .../GenerateCourse/AICourseOutlineHeader.tsx  |  5 ---
 .../GenerateCourse/AICourseRoadmapView.tsx    | 36 +++++++++++++++++--
 .../GenerateCourse/GenerateAICourse.tsx       |  3 ++
 src/components/GenerateCourse/GetAICourse.tsx |  8 +----
 .../GenerateCourse/RegenerateLesson.tsx       | 19 +++++++++-
 9 files changed, 101 insertions(+), 21 deletions(-)

diff --git a/src/components/GenerateCourse/AICourseContent.tsx b/src/components/GenerateCourse/AICourseContent.tsx
index 462404049..ba484cd76 100644
--- a/src/components/GenerateCourse/AICourseContent.tsx
+++ b/src/components/GenerateCourse/AICourseContent.tsx
@@ -513,6 +513,10 @@ export function AICourseContent(props: AICourseContentProps) {
               setExpandedModules={setExpandedModules}
               onUpgradeClick={() => setShowUpgradeModal(true)}
               viewMode={viewMode}
+              isForkable={isForkable}
+              onForkCourse={() => {
+                setIsForkingCourse(true);
+              }}
             />
           )}
 
diff --git a/src/components/GenerateCourse/AICourseLesson.tsx b/src/components/GenerateCourse/AICourseLesson.tsx
index dd4a443a2..9ef6e0a49 100644
--- a/src/components/GenerateCourse/AICourseLesson.tsx
+++ b/src/components/GenerateCourse/AICourseLesson.tsx
@@ -40,6 +40,7 @@ import {
   ResizablePanel,
   ResizablePanelGroup,
 } from './Resizeable';
+import { showLoginPopup } from '../../lib/popup';
 
 function getQuestionsFromResult(result: string) {
   const matchedQuestions = result.match(
@@ -301,13 +302,13 @@ export function AICourseLesson(props: AICourseLessonProps) {
                 </div>
               )}
 
-              <div className="mb-4 flex max-sm:flex-col-reverse justify-between">
+              <div className="mb-4 flex justify-between max-sm:flex-col-reverse">
                 <div className="text-sm text-gray-500">
                   Lesson {activeLessonIndex + 1} of {totalLessons}
                 </div>
 
                 {!isGenerating && !isLoading && (
-                  <div className="md:absolute top-2 right-2 flex items-center max-sm:justify-end gap-2 lg:top-6 lg:right-6 mb-3">
+                  <div className="top-2 right-2 mb-3 flex items-center gap-2 max-sm:justify-end md:absolute lg:top-6 lg:right-6">
                     <button
                       onClick={() => setIsAIChatsOpen(!isAIChatsOpen)}
                       className="rounded-full p-1 text-gray-400 hover:text-black max-lg:hidden"
@@ -345,6 +346,11 @@ export function AICourseLesson(props: AICourseLessonProps) {
                           : 'bg-green-500 hover:bg-green-600',
                       )}
                       onClick={() => {
+                        if (!isLoggedIn()) {
+                          showLoginPopup();
+                          return;
+                        }
+
                         if (isForkable) {
                           onForkCourse();
                           return;
@@ -429,10 +435,18 @@ export function AICourseLesson(props: AICourseLessonProps) {
 
               {!isLoggedIn() && (
                 <div className="mt-8 flex min-h-[152px] flex-col items-center justify-center gap-3 rounded-lg border border-gray-200 p-8">
-                  <LockIcon className="size-7 stroke-2 text-gray-400/90" />
+                  <LockIcon className="size-10 stroke-2 text-gray-400/90" />
                   <p className="text-sm text-gray-500">
                     Please login to generate course content
                   </p>
+                  <button
+                    onClick={() => {
+                      showLoginPopup();
+                    }}
+                    className="rounded-full bg-black px-4 py-1 text-sm text-white hover:bg-gray-800"
+                  >
+                    Login to Continue
+                  </button>
                 </div>
               )}
 
diff --git a/src/components/GenerateCourse/AICourseLessonChat.tsx b/src/components/GenerateCourse/AICourseLessonChat.tsx
index 4e6fc081e..442fb553a 100644
--- a/src/components/GenerateCourse/AICourseLessonChat.tsx
+++ b/src/components/GenerateCourse/AICourseLessonChat.tsx
@@ -34,6 +34,7 @@ import { queryClient } from '../../stores/query-client';
 import { billingDetailsOptions } from '../../queries/billing';
 import { ResizablePanel } from './Resizeable';
 import { Spinner } from '../ReactIcons/Spinner';
+import { showLoginPopup } from '../../lib/popup';
 
 export type AllowedAIChatRole = 'user' | 'assistant';
 export type AIChatHistoryType = {
@@ -324,8 +325,8 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
           className="relative flex items-start border-t border-gray-200 text-sm"
           onSubmit={handleChatSubmit}
         >
-          {isLimitExceeded && (
-            <div className="absolute inset-0 flex items-center justify-center gap-2 bg-black text-white">
+          {isLimitExceeded && isLoggedIn() && (
+            <div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
               <LockIcon
                 className="size-4 cursor-not-allowed"
                 strokeWidth={2.5}
@@ -346,6 +347,23 @@ export function AICourseLessonChat(props: AICourseLessonChatProps) {
               )}
             </div>
           )}
+          {!isLoggedIn() && (
+            <div className="absolute inset-0 z-10 flex items-center justify-center gap-2 bg-black text-white">
+              <LockIcon
+                className="size-4 cursor-not-allowed"
+                strokeWidth={2.5}
+              />
+              <p className="cursor-not-allowed">Please login to continue</p>
+              <button
+                onClick={() => {
+                  showLoginPopup();
+                }}
+                className="rounded-md bg-white px-2 py-1 text-xs font-medium text-black hover:bg-gray-300"
+              >
+                Login to Chat
+              </button>
+            </div>
+          )}
           <TextareaAutosize
             className={cn(
               'h-full min-h-[41px] grow resize-none bg-transparent px-4 py-2 focus:outline-hidden',
diff --git a/src/components/GenerateCourse/AICourseLimit.tsx b/src/components/GenerateCourse/AICourseLimit.tsx
index f2c80a80b..d41a4d699 100644
--- a/src/components/GenerateCourse/AICourseLimit.tsx
+++ b/src/components/GenerateCourse/AICourseLimit.tsx
@@ -4,6 +4,7 @@ import { getPercentage } from '../../lib/number';
 import { getAiCourseLimitOptions } from '../../queries/ai-course';
 import { billingDetailsOptions } from '../../queries/billing';
 import { queryClient } from '../../stores/query-client';
+import { isLoggedIn } from '../../lib/jwt';
 
 type AICourseLimitProps = {
   onUpgrade: () => void;
@@ -21,6 +22,10 @@ export function AICourseLimit(props: AICourseLimitProps) {
   const { data: userBillingDetails, isLoading: isBillingDetailsLoading } =
     useQuery(billingDetailsOptions(), queryClient);
 
+  if (!isLoggedIn()) {
+    return null;
+  }
+
   if (isLoading || !limits || isBillingDetailsLoading || !userBillingDetails) {
     return (
       <div className="hidden h-[38px] w-[208.09px] animate-pulse rounded-lg border border-gray-200 bg-gray-200 lg:block"></div>
diff --git a/src/components/GenerateCourse/AICourseOutlineHeader.tsx b/src/components/GenerateCourse/AICourseOutlineHeader.tsx
index 386efc7cb..d099bfa83 100644
--- a/src/components/GenerateCourse/AICourseOutlineHeader.tsx
+++ b/src/components/GenerateCourse/AICourseOutlineHeader.tsx
@@ -69,11 +69,6 @@ export function AICourseOutlineHeader(props: AICourseOutlineHeaderProps) {
               </button>
               <button
                 onClick={() => {
-                  if (isForkable) {
-                    onForkCourse();
-                    return;
-                  }
-
                   setViewMode('roadmap');
                 }}
                 className={cn(
diff --git a/src/components/GenerateCourse/AICourseRoadmapView.tsx b/src/components/GenerateCourse/AICourseRoadmapView.tsx
index abfbfb6dd..05437a99a 100644
--- a/src/components/GenerateCourse/AICourseRoadmapView.tsx
+++ b/src/components/GenerateCourse/AICourseRoadmapView.tsx
@@ -17,13 +17,15 @@ import {
 } from 'react';
 import type { AICourseViewMode } from './AICourseContent';
 import { replaceChildren } from '../../lib/dom';
-import { Frown, Loader2Icon } from 'lucide-react';
+import { Frown, Loader2Icon, LockIcon } from 'lucide-react';
 import { renderTopicProgress } from '../../lib/resource-progress';
 import { queryClient } from '../../stores/query-client';
 import { useQuery } from '@tanstack/react-query';
 import { billingDetailsOptions } from '../../queries/billing';
 import { AICourseOutlineHeader } from './AICourseOutlineHeader';
 import type { AiCourse } from '../../lib/ai';
+import { showLoginPopup } from '../../lib/popup';
+import { isLoggedIn } from '../../lib/jwt';
 
 export type AICourseRoadmapViewProps = {
   done: string[];
@@ -37,6 +39,8 @@ export type AICourseRoadmapViewProps = {
   onUpgradeClick: () => void;
   setExpandedModules: Dispatch<SetStateAction<Record<number, boolean>>>;
   viewMode: AICourseViewMode;
+  isForkable: boolean;
+  onForkCourse: () => void;
 };
 
 export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
@@ -52,6 +56,8 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
     setExpandedModules,
     onUpgradeClick,
     viewMode,
+    isForkable,
+    onForkCourse,
   } = props;
 
   const containerEl = useRef<HTMLDivElement>(null);
@@ -66,6 +72,11 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
   const isPaidUser = userBillingDetails?.status === 'active';
 
   const generateAICourseRoadmap = async (courseSlug: string) => {
+    if (!isLoggedIn()) {
+      setIsGenerating(false);
+      return;
+    }
+
     try {
       const response = await fetch(
         `${import.meta.env.PUBLIC_API_URL}/v1-generate-ai-course-roadmap/${courseSlug}`,
@@ -216,6 +227,8 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
         }}
         viewMode={viewMode}
         setViewMode={setViewMode}
+        isForkable={isForkable}
+        onForkCourse={onForkCourse}
       />
       {isLoading && (
         <div className="absolute inset-0 flex h-full w-full items-center justify-center">
@@ -223,10 +236,27 @@ export function AICourseRoadmapView(props: AICourseRoadmapViewProps) {
         </div>
       )}
 
-      {error && !isGenerating && (
+      {!isLoggedIn() && (
+        <div className="absolute inset-0 flex h-full w-full flex-col items-center justify-center gap-2">
+          <LockIcon className="size-10 stroke-2 text-gray-400/90" />
+          <p className="text-sm text-gray-500">
+            Please login to generate course content
+          </p>
+          <button
+            onClick={() => {
+              showLoginPopup();
+            }}
+            className="rounded-full bg-black px-4 py-1 text-sm text-white hover:bg-gray-800"
+          >
+            Login to Continue
+          </button>
+        </div>
+      )}
+
+      {error && !isGenerating && !isLoggedIn() && (
         <div className="absolute inset-0 flex h-full w-full flex-col items-center justify-center">
           <Frown className="size-20 text-red-500" />
-          <p className="mx-auto mt-5 max-w-[250px] text-balance text-center text-base text-red-500">
+          <p className="mx-auto mt-5 max-w-[250px] text-center text-base text-balance text-red-500">
             {error || 'Something went wrong'}
           </p>
 
diff --git a/src/components/GenerateCourse/GenerateAICourse.tsx b/src/components/GenerateCourse/GenerateAICourse.tsx
index 8b901ad94..092cc0964 100644
--- a/src/components/GenerateCourse/GenerateAICourse.tsx
+++ b/src/components/GenerateCourse/GenerateAICourse.tsx
@@ -7,6 +7,7 @@ import { generateCourse } from '../../helper/generate-ai-course';
 import { useQuery } from '@tanstack/react-query';
 import { getAiCourseOptions } from '../../queries/ai-course';
 import { queryClient } from '../../stores/query-client';
+import { useAuth } from '../../hooks/use-auth';
 
 type GenerateAICourseProps = {};
 
@@ -20,6 +21,7 @@ export function GenerateAICourse(props: GenerateAICourseProps) {
 
   const [isLoading, setIsLoading] = useState(true);
   const [error, setError] = useState('');
+  const currentUser = useAuth();
 
   const [courseId, setCourseId] = useState('');
   const [courseSlug, setCourseSlug] = useState('');
@@ -150,6 +152,7 @@ export function GenerateAICourse(props: GenerateAICourseProps) {
   return (
     <AICourseContent
       courseSlug={courseSlug}
+      creatorId={currentUser?.id}
       course={course}
       isLoading={isLoading}
       error={error}
diff --git a/src/components/GenerateCourse/GetAICourse.tsx b/src/components/GenerateCourse/GetAICourse.tsx
index dfe45de9f..072b80689 100644
--- a/src/components/GenerateCourse/GetAICourse.tsx
+++ b/src/components/GenerateCourse/GetAICourse.tsx
@@ -20,17 +20,11 @@ export function GetAICourse(props: GetAICourseProps) {
   const { data: aiCourse, error: queryError } = useQuery(
     {
       ...getAiCourseOptions({ aiCourseSlug: courseSlug }),
-      enabled: !!courseSlug && !!isLoggedIn(),
+      enabled: !!courseSlug,
     },
     queryClient,
   );
 
-  useEffect(() => {
-    if (!isLoggedIn()) {
-      window.location.href = '/ai';
-    }
-  }, [isLoggedIn]);
-
   useEffect(() => {
     if (!aiCourse) {
       return;
diff --git a/src/components/GenerateCourse/RegenerateLesson.tsx b/src/components/GenerateCourse/RegenerateLesson.tsx
index 507d106b9..5bd468f51 100644
--- a/src/components/GenerateCourse/RegenerateLesson.tsx
+++ b/src/components/GenerateCourse/RegenerateLesson.tsx
@@ -4,6 +4,8 @@ import { useOutsideClick } from '../../hooks/use-outside-click';
 import { cn } from '../../lib/classname';
 import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
 import { ModifyCoursePrompt } from './ModifyCoursePrompt';
+import { isLoggedIn } from '../../lib/jwt';
+import { showLoginPopup } from '../../lib/popup';
 
 type RegenerateLessonProps = {
   onRegenerateLesson: (prompt?: string) => void;
@@ -39,6 +41,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
           onClose={() => setShowPromptModal(false)}
           onSubmit={(prompt) => {
             setShowPromptModal(false);
+            if (!isLoggedIn()) {
+              showLoginPopup();
+              return;
+            }
+
             if (isForkable) {
               onForkCourse();
               return;
@@ -49,7 +56,7 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
         />
       )}
 
-      <div className="relative lg:mr-1 flex items-center" ref={ref}>
+      <div className="relative flex items-center lg:mr-1" ref={ref}>
         <button
           className={cn('rounded-full p-1 text-gray-400 hover:text-black', {
             'text-black': isDropdownVisible,
@@ -63,6 +70,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
             <button
               onClick={() => {
                 setIsDropdownVisible(false);
+                if (!isLoggedIn()) {
+                  showLoginPopup();
+                  return;
+                }
+
                 if (isForkable) {
                   onForkCourse();
                   return;
@@ -82,6 +94,11 @@ export function RegenerateLesson(props: RegenerateLessonProps) {
             <button
               onClick={() => {
                 setIsDropdownVisible(false);
+                if (!isLoggedIn()) {
+                  showLoginPopup();
+                  return;
+                }
+
                 if (isForkable) {
                   onForkCourse();
                   return;

From c28593142eca6fd832fca908ea3a23a1e9b74fe5 Mon Sep 17 00:00:00 2001
From: Arik Chakma <arikchangma@gmail.com>
Date: Sat, 12 Apr 2025 01:32:57 +0600
Subject: [PATCH 31/31] fix: next lesson

---
 src/components/GenerateCourse/AICourseLesson.tsx | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/components/GenerateCourse/AICourseLesson.tsx b/src/components/GenerateCourse/AICourseLesson.tsx
index 9ef6e0a49..28c053521 100644
--- a/src/components/GenerateCourse/AICourseLesson.tsx
+++ b/src/components/GenerateCourse/AICourseLesson.tsx
@@ -477,6 +477,11 @@ export function AICourseLesson(props: AICourseLessonProps) {
                 <div>
                   <button
                     onClick={() => {
+                      if (!isLoggedIn()) {
+                        onGoToNextLesson();
+                        return;
+                      }
+
                       if (!isLessonDone) {
                         toggleDone(undefined, {
                           onSuccess: () => {