mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-09-25 00:21:28 +02:00
Guide listing UI update
This commit is contained in:
@@ -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';
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
146
src/components/GenerateGuide/UserGuidesList.tsx
Normal file
146
src/components/GenerateGuide/UserGuidesList.tsx
Normal 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();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import { ListUserAIGuides } from '../../components/GenerateGuide/ListUserAIGuides';
|
||||
import { UserGuidesList } from '../../components/GenerateGuide/UserGuidesList';
|
||||
import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
|
||||
import { AILibraryLayout } from '../../components/AIGuide/AILibraryLayout';
|
||||
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.'
|
||||
>
|
||||
<AILibraryLayout activeTab='guides' client:load>
|
||||
<ListUserAIGuides client:load />
|
||||
<UserGuidesList client:load />
|
||||
</AILibraryLayout>
|
||||
</SkeletonLayout>
|
||||
|
Reference in New Issue
Block a user