From 4fbea4680c14f1955d4ee68809af52962fd10020 Mon Sep 17 00:00:00 2001 From: Kamran Ahmed Date: Thu, 10 Apr 2025 15:34:32 +0100 Subject: [PATCH] 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 ( - <> -
-
-

Staff Picks

-
-
+ if (isInitialLoading || isFeaturedAiCoursesLoading) { + return ( + + ); + } - {(isFeaturedAiCoursesLoading || isInitialLoading) && ( -
- -

Loading...

-
- )} + if (courses.length === 0) { + return ( + { + window.location.href = '/ai'; + }} + /> + ); + } + + return ( +
+ {!isFeaturedAiCoursesLoading && courses && courses.length > 0 && (
- {courses.map((course) => ( - - ))} +
+ {courses.map((course) => ( + + ))} +
)} - +
); } 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 ( +
+
+

{title}

+
+ +
+ setShowUpgradePopup(true)} + /> + + {children} +
+
+ ); +} 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 ( +
+

+ + {limitUsedPercentage}% of daily limit used{' '} + + + {limitUsedPercentage}% used + + +

+
+ ); +} \ 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 && ( setShowUpgradePopup(false)} /> )} -
-
-

- Your Courses -

-
-
- {used > 0 && limit > 0 && !isPaidUserLoading && ( -
-

- - {limitUsedPercentage}% of daily limit used{' '} - - - {limitUsedPercentage}% used - - -

-
- )} - - { - setPageState({ - ...pageState, - query: value, - currPage: '1', - }); - }} - /> -
-
+ + { + setPageState({ + ...pageState, + query: value, + currPage: '1', + }); + }} + /> + {!isUserAiCoursesLoading && courses && courses.length > 0 && (
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.' > -
-
- -
-
+