Implement pagination of ai tutor ai courses

feat/ai-tutor-redesign
Kamran Ahmed 3 days ago
parent ab981b8c88
commit 2868fa3c27
  1. 107
      src/components/AITutor/AIExploreCourseListing.tsx
  2. 50
      src/queries/ai-course.ts

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

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

Loading…
Cancel
Save