1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 08:35:42 +02:00

Guide listing UI update

This commit is contained in:
Kamran Ahmed
2025-06-18 23:39:01 +01:00
parent 0503b64cba
commit 2a61e6aab6
3 changed files with 148 additions and 152 deletions

View File

@@ -1,150 +0,0 @@
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 {
listUserAIGuidesOptions,
type ListUserAIGuidesQuery,
} from '../../queries/ai-guide';
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 { AILoadingState } from '../AITutor/AILoadingState';
import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
import { AIGuideCard } from '../AIGuide/AIGuideCard';
export function ListUserAIGuides() {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [showUpgradePopup, setShowUpgradePopup] = useState(false);
const [pageState, setPageState] = useState<ListUserAIGuidesQuery>({
perPage: '21',
currPage: '1',
query: '',
});
const { data: userAiGuides, isFetching: isUserAiGuidesLoading } = useQuery(
listUserAIGuidesOptions(pageState),
queryClient,
);
useEffect(() => {
setIsInitialLoading(false);
}, [userAiGuides]);
const guides = userAiGuides?.data ?? [];
useEffect(() => {
const queryParams = getUrlParams();
setPageState({
...pageState,
currPage: queryParams?.p || '1',
query: queryParams?.q || '',
});
}, []);
useEffect(() => {
if (pageState?.currPage !== '1' || pageState?.query !== '') {
setUrlParams({
p: pageState?.currPage || '1',
q: pageState?.query || '',
});
} else {
deleteUrlParam('p');
deleteUrlParam('q');
}
}, [pageState]);
if (isUserAiGuidesLoading || isInitialLoading) {
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();
}}
/>
);
}
return (
<>
{showUpgradePopup && (
<UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
)}
<AITutorHeader
title="Your Guides"
onUpgradeClick={() => setShowUpgradePopup(true)}
>
<AICourseSearch
value={pageState?.query || ''}
onChange={(value) => {
setPageState({
...pageState,
query: value,
currPage: '1',
});
}}
placeholder="Search guides..."
/>
</AITutorHeader>
{(isUserAiGuidesLoading || isInitialLoading) && (
<AILoadingState
title="Loading your guides"
subtitle="This may take a moment..."
/>
)}
{!isUserAiGuidesLoading && !isInitialLoading && guides.length > 0 && (
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3">
{guides.map((guide) => (
<AIGuideCard key={guide._id} guide={guide} />
))}
</div>
<Pagination
totalCount={userAiGuides?.totalCount || 0}
totalPages={userAiGuides?.totalPages || 0}
currPage={Number(userAiGuides?.currPage || 1)}
perPage={Number(userAiGuides?.perPage || 10)}
onPageChange={(page) => {
setPageState({ ...pageState, currPage: String(page) });
}}
className="rounded-lg border border-gray-200 bg-white p-4"
/>
</div>
)}
{!isUserAiGuidesLoading && !isInitialLoading && guides.length === 0 && (
<AITutorTallMessage
title="No guides found"
subtitle="You haven't generated any guides yet."
icon={BookOpen}
buttonText="Create your first guide"
onButtonClick={() => {
window.location.href = '/ai';
}}
/>
)}
</>
);
}

View File

@@ -0,0 +1,146 @@
import { useQuery } from '@tanstack/react-query';
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 {
listUserAIGuidesOptions,
type ListUserAIGuidesQuery,
} from '../../queries/ai-guide';
import { queryClient } from '../../stores/query-client';
import { AITutorTallMessage } from '../AITutor/AITutorTallMessage';
import { UpgradeAccountModal } from '../Billing/UpgradeAccountModal';
import { Pagination } from '../Pagination/Pagination';
import { AICourseSearch } from '../GenerateCourse/AICourseSearch';
import { AIGuideCard } from '../AIGuide/AIGuideCard';
export function UserGuidesList() {
const [isInitialLoading, setIsInitialLoading] = useState(true);
const [showUpgradePopup, setShowUpgradePopup] = useState(false);
const [pageState, setPageState] = useState<ListUserAIGuidesQuery>({
perPage: '21',
currPage: '1',
query: '',
});
const { data: userAiGuides, isFetching: isUserAiGuidesLoading } = useQuery(
listUserAIGuidesOptions(pageState),
queryClient,
);
useEffect(() => {
setIsInitialLoading(false);
}, [userAiGuides]);
const guides = userAiGuides?.data ?? [];
useEffect(() => {
const queryParams = getUrlParams();
setPageState({
...pageState,
currPage: queryParams?.p || '1',
query: queryParams?.q || '',
});
}, []);
useEffect(() => {
if (pageState?.currPage !== '1' || pageState?.query !== '') {
setUrlParams({
p: pageState?.currPage || '1',
q: pageState?.query || '',
});
} else {
deleteUrlParam('p');
deleteUrlParam('q');
}
}, [pageState]);
const isUserAuthenticated = isLoggedIn();
const isAnyLoading = isUserAiGuidesLoading || isInitialLoading;
return (
<>
{showUpgradePopup && (
<UpgradeAccountModal onClose={() => setShowUpgradePopup(false)} />
)}
<AICourseSearch
value={pageState?.query || ''}
onChange={(value) => {
setPageState({
...pageState,
query: value,
currPage: '1',
});
}}
disabled={isAnyLoading}
placeholder="Search guides..."
/>
{isAnyLoading && (
<p className="mb-4 flex flex-row items-center gap-2 text-sm text-gray-500">
<Loader2 className="h-4 w-4 animate-spin" />
Loading your guides...
</p>
)}
{!isAnyLoading && (
<>
<p className="mb-4 text-sm text-gray-500">
You have generated {userAiGuides?.totalCount} guides so far.
</p>
{isUserAuthenticated && !isAnyLoading && guides.length > 0 && (
<div className="flex flex-col gap-2">
<div className="grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3">
{guides.map((guide) => (
<AIGuideCard key={guide._id} guide={guide} />
))}
</div>
<Pagination
totalCount={userAiGuides?.totalCount || 0}
totalPages={userAiGuides?.totalPages || 0}
currPage={Number(userAiGuides?.currPage || 1)}
perPage={Number(userAiGuides?.perPage || 10)}
onPageChange={(page) => {
setPageState({ ...pageState, currPage: String(page) });
}}
className="rounded-lg border border-gray-200 bg-white p-4"
/>
</div>
)}
{!isAnyLoading && guides.length === 0 && (
<AITutorTallMessage
title={
isUserAuthenticated ? 'No guides found' : 'Sign up or login'
}
subtitle={
isUserAuthenticated
? "You haven't generated any guides yet."
: 'Takes 2s to sign up and generate your first guide.'
}
icon={BookOpen}
buttonText={
isUserAuthenticated
? 'Create your first guide'
: 'Sign up or login'
}
onButtonClick={() => {
if (isUserAuthenticated) {
window.location.href = '/ai';
} else {
showLoginPopup();
}
}}
/>
)}
</>
)}
</>
);
}

View File

@@ -1,5 +1,5 @@
--- ---
import { ListUserAIGuides } from '../../components/GenerateGuide/ListUserAIGuides'; import { UserGuidesList } from '../../components/GenerateGuide/UserGuidesList';
import SkeletonLayout from '../../layouts/SkeletonLayout.astro'; import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
import { AILibraryLayout } from '../../components/AIGuide/AILibraryLayout'; import { AILibraryLayout } from '../../components/AIGuide/AILibraryLayout';
const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png'; const ogImage = 'https://roadmap.sh/og-images/ai-tutor.png';
@@ -12,6 +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.'
> >
<AILibraryLayout activeTab='guides' client:load> <AILibraryLayout activeTab='guides' client:load>
<ListUserAIGuides client:load /> <UserGuidesList client:load />
</AILibraryLayout> </AILibraryLayout>
</SkeletonLayout> </SkeletonLayout>