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] 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>