AI landing page changes

feat/ai-tutor-redesign
Kamran Ahmed 2 weeks ago
parent d1208047a5
commit d0f8fc4e6a
  1. 27
      src/components/AITutor/AILoadingState.tsx
  2. 4
      src/components/AITutor/AITutorLayout.tsx
  3. 31
      src/components/AITutor/AITutorTallMessage.tsx
  4. 7
      src/components/GenerateCourse/AICourse.tsx
  5. 12
      src/components/GenerateCourse/AICourseCard.tsx
  6. 80
      src/components/GenerateCourse/UserCoursesList.tsx
  7. 4
      src/pages/ai/courses.astro

@ -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>
);
}

@ -11,7 +11,9 @@ export function AITutorLayout(props: AITutorLayoutProps) {
return ( return (
<div className="flex flex-grow flex-row"> <div className="flex flex-grow flex-row">
<AITutorSidebar activeTab={activeTab} /> <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> </div>
); );
} }

@ -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>
);
}

@ -3,7 +3,6 @@ import { useEffect, useState } from 'react';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { UserCoursesList } from './UserCoursesList';
import { FineTuneCourse } from './FineTuneCourse'; import { FineTuneCourse } from './FineTuneCourse';
import { import {
clearFineTuneData, clearFineTuneData,
@ -72,10 +71,9 @@ export function AICourse(props: AICourseProps) {
} }
return ( return (
<section className="flex grow flex-col bg-gray-100"> <div className="flex w-full max-w-3xl mx-auto flex-grow flex-col justify-center">
<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"> <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 What can I help you learn?
</h1> </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-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 Enter a topic below to generate a personalized course for it
@ -163,6 +161,5 @@ export function AICourse(props: AICourseProps) {
</form> </form>
</div> </div>
</div> </div>
</section>
); );
} }

@ -12,14 +12,6 @@ type AICourseCardProps = {
export function AICourseCard(props: AICourseCardProps) { export function AICourseCard(props: AICourseCardProps) {
const { course, showActions = true, showProgress = true } = props; 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 // Map difficulty to color
const difficultyColor = const difficultyColor =
{ {
@ -35,10 +27,10 @@ export function AICourseCard(props: AICourseCardProps) {
totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0; totalTopics > 0 ? Math.round((completedTopics / totalTopics) * 100) : 0;
return ( return (
<div className="relative"> <div className="relative flex flex-grow flex-col">
<a <a
href={`/ai/${course.slug}`} 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"> <div className="flex items-center justify-between">
<span <span

@ -7,7 +7,7 @@ import {
import { queryClient } from '../../stores/query-client'; import { queryClient } from '../../stores/query-client';
import { AICourseCard } from './AICourseCard'; import { AICourseCard } from './AICourseCard';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Gift, Loader2, User2 } from 'lucide-react'; import { BookOpen, Gift } from 'lucide-react';
import { isLoggedIn } from '../../lib/jwt'; import { isLoggedIn } from '../../lib/jwt';
import { showLoginPopup } from '../../lib/popup'; import { showLoginPopup } from '../../lib/popup';
import { cn } from '../../lib/classname'; import { cn } from '../../lib/classname';
@ -16,6 +16,8 @@ import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser'; import { getUrlParams, setUrlParams, deleteUrlParam } from '../../lib/browser';
import { AICourseSearch } from './AICourseSearch'; import { AICourseSearch } from './AICourseSearch';
import { Pagination } from '../Pagination/Pagination'; import { Pagination } from '../Pagination/Pagination';
import { AILoadingState } from '../AITutor/AILoadingState';
import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
type UserCoursesListProps = {}; type UserCoursesListProps = {};
@ -72,6 +74,43 @@ export function UserCoursesList(props: UserCoursesListProps) {
} }
}, [pageState]); }, [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 ( return (
<> <>
{showUpgradePopup && ( {showUpgradePopup && (
@ -105,7 +144,7 @@ export function UserCoursesList(props: UserCoursesListProps) {
onClick={() => { onClick={() => {
setShowUpgradePopup(true); 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" /> <Gift className="size-4" />
Upgrade Upgrade
@ -127,46 +166,13 @@ export function UserCoursesList(props: UserCoursesListProps) {
</div> </div>
</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 && ( {!isUserAiCoursesLoading && courses && courses.length > 0 && (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="grid grid-cols-3 gap-2">
{courses.map((course) => ( {courses.map((course) => (
<AICourseCard key={course._id} course={course} /> <AICourseCard key={course._id} course={course} />
))} ))}
</div>
<Pagination <Pagination
totalCount={userAiCourses?.totalCount || 0} totalCount={userAiCourses?.totalCount || 0}

@ -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.' 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> <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 /> <UserCoursesList client:load />
</div>
</section>
</AITutorLayout> </AITutorLayout>
</SkeletonLayout> </SkeletonLayout>

Loading…
Cancel
Save