From 0503b64cbaacaae790dfba2c68b56db126d4b5d0 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Wed, 18 Jun 2025 22:51:19 +0100 Subject: [PATCH] UI improvements for ai library --- src/components/AIGuide/AILibraryLayout.tsx | 16 +- src/components/AITutor/AITutorHeader.tsx | 41 +++-- .../GenerateCourse/AICourseCard.tsx | 76 ++++----- .../GenerateCourse/AICourseSearch.tsx | 8 +- .../GenerateCourse/UserCoursesList.tsx | 146 +++++++++--------- src/components/Library/LibraryTab.tsx | 8 +- 6 files changed, 153 insertions(+), 142 deletions(-) diff --git a/src/components/AIGuide/AILibraryLayout.tsx b/src/components/AIGuide/AILibraryLayout.tsx index e7ae3a995..fb2173aac 100644 --- a/src/components/AIGuide/AILibraryLayout.tsx +++ b/src/components/AIGuide/AILibraryLayout.tsx @@ -1,4 +1,7 @@ +import { useState } from 'react'; +import { AITutorHeader } from '../AITutor/AITutorHeader'; import { AITutorLayout } from '../AITutor/AITutorLayout'; +import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal'; import { LibraryTabs } from '../Library/LibraryTab'; type AILibraryLayoutProps = { @@ -9,9 +12,20 @@ type AILibraryLayoutProps = { export function AILibraryLayout(props: AILibraryLayoutProps) { const { activeTab, children } = props; + const [showUpgradePopup, setShowUpgradePopup] = useState(false); + return ( -
+ {showUpgradePopup && ( + setShowUpgradePopup(false)} /> + )} +
+ setShowUpgradePopup(true)} + /> + {children}
diff --git a/src/components/AITutor/AITutorHeader.tsx b/src/components/AITutor/AITutorHeader.tsx index 154a87865..aaac924f4 100644 --- a/src/components/AITutor/AITutorHeader.tsx +++ b/src/components/AITutor/AITutorHeader.tsx @@ -3,15 +3,17 @@ import { AITutorLimits } from './AITutorLimits'; import { getAiCourseLimitOptions } from '../../queries/ai-course'; import { queryClient } from '../../stores/query-client'; import { useIsPaidUser } from '../../queries/billing'; +import { PlusIcon } from 'lucide-react'; type AITutorHeaderProps = { title: string; + subtitle?: string; onUpgradeClick: () => void; children?: React.ReactNode; }; export function AITutorHeader(props: AITutorHeaderProps) { - const { title, onUpgradeClick, children } = props; + const { title, subtitle, onUpgradeClick, children } = props; const { data: limits } = useQuery(getAiCourseLimitOptions(), queryClient); const { isPaidUser, isLoading: isPaidUserLoading } = useIsPaidUser(); @@ -20,20 +22,29 @@ export function AITutorHeader(props: AITutorHeaderProps) { return (
-
-

{title}

-
- -
- - - {children} +
+
+

+ {title} +

+ {subtitle &&

{subtitle}

} +
+
); diff --git a/src/components/GenerateCourse/AICourseCard.tsx b/src/components/GenerateCourse/AICourseCard.tsx index 04dad0e3b..027095930 100644 --- a/src/components/GenerateCourse/AICourseCard.tsx +++ b/src/components/GenerateCourse/AICourseCard.tsx @@ -28,62 +28,50 @@ export function AICourseCard(props: AICourseCardProps) { const updatedAgo = getRelativeTimeString(course?.updatedAt); return ( -
+
-
- - {course.difficulty} - + {/* Title and difficulty section */} +
+
+ + {course.difficulty} + +
+ +

+ {course.title} +

-

- {course.title} -

- -
-
+ {/* Course stats section */} +
+
{modulesCount} modules
- - -
- - {totalTopics} lessons -
-
- - {showProgress && totalTopics > 0 && ( -
-
- Progress - - - {progressPercentage}% - +
+
+ + {totalTopics} lessons
-
-
-
-
-
+ {showProgress && totalTopics > 0 && ( + <> + +
+ + {progressPercentage}% complete + +
+ + )}
- )} - -
- - Last updated {updatedAgo} -
diff --git a/src/components/GenerateCourse/AICourseSearch.tsx b/src/components/GenerateCourse/AICourseSearch.tsx index b2f22a6aa..9b9da1f54 100644 --- a/src/components/GenerateCourse/AICourseSearch.tsx +++ b/src/components/GenerateCourse/AICourseSearch.tsx @@ -6,10 +6,11 @@ type AICourseSearchProps = { value: string; onChange: (value: string) => void; placeholder?: string; + disabled?: boolean; }; export function AICourseSearch(props: AICourseSearchProps) { - const { value: defaultValue, onChange, placeholder } = props; + const { value: defaultValue, onChange, placeholder, disabled } = props; const [searchTerm, setSearchTerm] = useState(defaultValue); const debouncedSearchTerm = useDebounceValue(searchTerm, 500); @@ -31,16 +32,17 @@ export function AICourseSearch(props: AICourseSearchProps) { }, [debouncedSearchTerm]); return ( -
+
setSearchTerm(e.target.value)} + disabled={disabled} />
); diff --git a/src/components/GenerateCourse/UserCoursesList.tsx b/src/components/GenerateCourse/UserCoursesList.tsx index 6a64f34ef..03da3aa99 100644 --- a/src/components/GenerateCourse/UserCoursesList.tsx +++ b/src/components/GenerateCourse/UserCoursesList.tsx @@ -1,21 +1,19 @@ import { useQuery } from '@tanstack/react-query'; -import { BookOpen } from 'lucide-react'; +import { BookOpen, Loader2 } 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 { listUserAiCoursesOptions, type ListUserAiCoursesQuery, } from '../../queries/ai-course'; import { queryClient } from '../../stores/query-client'; -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'; +import { isLoggedIn } from '../../lib/jwt'; +import { showLoginPopup } from '../../lib/popup'; export function UserCoursesList() { const [isInitialLoading, setIsInitialLoading] = useState(true); @@ -60,28 +58,8 @@ export function UserCoursesList() { } }, [pageState]); - if (isUserAiCoursesLoading || isInitialLoading) { - return ( - - ); - } - - if (!isLoggedIn()) { - return ( - { - showLoginPopup(); - }} - /> - ); - } + const isUserAuthenticated = isLoggedIn(); + const isAnyLoading = isUserAiCoursesLoading || isInitialLoading; return ( <> @@ -89,60 +67,76 @@ export function UserCoursesList() { setShowUpgradePopup(false)} /> )} - setShowUpgradePopup(true)} - > - { - setPageState({ - ...pageState, - query: value, - currPage: '1', - }); - }} - /> - + { + setPageState({ + ...pageState, + query: value, + currPage: '1', + }); + }} + disabled={isAnyLoading} + /> - {(isUserAiCoursesLoading || isInitialLoading) && ( - + {isAnyLoading && ( +

+ + Loading your courses... +

)} - {!isUserAiCoursesLoading && !isInitialLoading && courses.length > 0 && ( -
-
- {courses.map((course) => ( - - ))} -
+ {!isAnyLoading && ( + <> +

+ You have generated {userAiCourses?.totalCount} courses so far. +

- { - setPageState({ ...pageState, currPage: String(page) }); - }} - className="rounded-lg border border-gray-200 bg-white p-4" - /> -
- )} + {isUserAuthenticated && !isAnyLoading && courses.length > 0 && ( +
+ {courses.map((course) => ( + + ))} - {!isUserAiCoursesLoading && !isInitialLoading && courses.length === 0 && ( - { - window.location.href = '/ai'; - }} - /> + { + setPageState({ ...pageState, currPage: String(page) }); + }} + className="rounded-lg border border-gray-200 bg-white p-4" + /> +
+ )} + + {!isAnyLoading && courses.length === 0 && ( + { + if (isUserAuthenticated) { + window.location.href = '/ai'; + } else { + showLoginPopup(); + } + }} + /> + )} + )} ); diff --git a/src/components/Library/LibraryTab.tsx b/src/components/Library/LibraryTab.tsx index 0f1bc8078..18d3c445f 100644 --- a/src/components/Library/LibraryTab.tsx +++ b/src/components/Library/LibraryTab.tsx @@ -9,7 +9,7 @@ export function LibraryTabs(props: LibraryTabsProps) { const { activeTab } = props; return ( -
+