mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2026-03-13 02:01:57 +08:00
Compare commits
2 Commits
fix/sql
...
ai/paginat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a7dba8335 | ||
|
|
7daf2e5d1d |
@@ -1,9 +1,10 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useToast } from '../../hooks/use-toast';
|
||||
import { httpGet } from '../../lib/http';
|
||||
import { getRelativeTimeString } from '../../lib/date';
|
||||
import { Eye, Loader2, RefreshCcw } from 'lucide-react';
|
||||
import { AIRoadmapAlert } from '../GenerateRoadmap/AIRoadmapAlert.tsx';
|
||||
import { formatCommaNumber } from '../../lib/number.ts';
|
||||
|
||||
export interface AIRoadmapDocument {
|
||||
_id?: string;
|
||||
@@ -27,39 +28,33 @@ export function ExploreAIRoadmap() {
|
||||
const toast = useToast();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||
const [roadmaps, setRoadmaps] = useState<AIRoadmapDocument[]>([]);
|
||||
const [currPage, setCurrPage] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(1);
|
||||
const [roadmapsResponse, setRoadmapsResponse] =
|
||||
useState<ExploreRoadmapsResponse | null>(null);
|
||||
|
||||
const loadAIRoadmaps = useCallback(
|
||||
async (currPage: number) => {
|
||||
const { response, error } = await httpGet<ExploreRoadmapsResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-ai-roadmaps`,
|
||||
{
|
||||
currPage,
|
||||
},
|
||||
);
|
||||
const loadAIRoadmaps = async (currPage: number) => {
|
||||
setIsLoading(true);
|
||||
const { response, error } = await httpGet<ExploreRoadmapsResponse>(
|
||||
`${import.meta.env.PUBLIC_API_URL}/v1-list-ai-roadmaps`,
|
||||
{
|
||||
currPage,
|
||||
},
|
||||
);
|
||||
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Something went wrong');
|
||||
return;
|
||||
}
|
||||
if (error || !response) {
|
||||
toast.error(error?.message || 'Something went wrong');
|
||||
return;
|
||||
}
|
||||
|
||||
const newRoadmaps = [...roadmaps, ...response.data];
|
||||
if (
|
||||
JSON.stringify(roadmaps) === JSON.stringify(response.data) ||
|
||||
JSON.stringify(roadmaps) === JSON.stringify(newRoadmaps)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setRoadmapsResponse(response);
|
||||
};
|
||||
|
||||
setRoadmaps(newRoadmaps);
|
||||
setCurrPage(response.currPage);
|
||||
setTotalPages(response.totalPages);
|
||||
},
|
||||
[currPage, roadmaps],
|
||||
);
|
||||
const currPage = roadmapsResponse?.currPage || 1;
|
||||
const totalPages = roadmapsResponse?.totalPages || 1;
|
||||
const totalCount = roadmapsResponse?.totalCount || 0;
|
||||
|
||||
const perPage = roadmapsResponse?.perPage || 0;
|
||||
const hasNextPage = currPage < totalPages;
|
||||
const hasPrevPage = currPage > 1;
|
||||
|
||||
useEffect(() => {
|
||||
loadAIRoadmaps(currPage).finally(() => {
|
||||
@@ -67,7 +62,51 @@ export function ExploreAIRoadmap() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
const hasMorePages = currPage < totalPages;
|
||||
const roadmaps = roadmapsResponse?.data || [];
|
||||
|
||||
const paginationBar = (
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
{hasPrevPage && (
|
||||
<button
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md border disabled:cursor-not-allowed disabled:opacity-65"
|
||||
onClick={() => {
|
||||
loadAIRoadmaps(currPage - 1).finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
←
|
||||
</button>
|
||||
)}
|
||||
{hasNextPage && (
|
||||
<button
|
||||
className="flex h-6 w-6 items-center justify-center rounded-md border disabled:cursor-not-allowed disabled:opacity-65"
|
||||
onClick={() => {
|
||||
loadAIRoadmaps(currPage + 1).finally(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
}}
|
||||
disabled={isLoading}
|
||||
>
|
||||
→
|
||||
</button>
|
||||
)}
|
||||
|
||||
<p className="text-sm">
|
||||
Showing {formatCommaNumber((currPage - 1) * perPage)} to{' '}
|
||||
{formatCommaNumber((currPage - 1) * perPage + roadmaps.length)} of{' '}
|
||||
{formatCommaNumber(totalCount)} entries
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center text-sm">
|
||||
<p>
|
||||
Page {formatCommaNumber(currPage)} of {formatCommaNumber(totalPages)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="container mx-auto py-3 sm:py-6">
|
||||
@@ -75,6 +114,8 @@ export function ExploreAIRoadmap() {
|
||||
<AIRoadmapAlert isListing />
|
||||
</div>
|
||||
|
||||
{paginationBar}
|
||||
|
||||
{isLoading ? (
|
||||
<ul className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{new Array(21).fill(0).map((_, index) => (
|
||||
@@ -90,7 +131,7 @@ export function ExploreAIRoadmap() {
|
||||
<div className="text-center text-gray-800">No roadmaps found</div>
|
||||
) : (
|
||||
<>
|
||||
<ul className="grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<ul className="mb-4 grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{roadmaps.map((roadmap) => {
|
||||
const roadmapLink = `/ai?id=${roadmap._id}`;
|
||||
return (
|
||||
@@ -119,27 +160,7 @@ export function ExploreAIRoadmap() {
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
{hasMorePages && (
|
||||
<div className="my-5 flex items-center justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsLoadingMore(true);
|
||||
loadAIRoadmaps(currPage + 1).finally(() => {
|
||||
setIsLoadingMore(false);
|
||||
});
|
||||
}}
|
||||
className="inline-flex items-center gap-1.5 rounded-full bg-black px-3 py-1.5 text-sm font-medium text-white shadow-xl transition-colors focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={isLoadingMore}
|
||||
>
|
||||
{isLoadingMore ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin stroke-[2.5]" />
|
||||
) : (
|
||||
<RefreshCcw className="h-4 w-4 stroke-[2.5]" />
|
||||
)}
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{paginationBar}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -194,7 +194,7 @@ export function GenerateRoadmap() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (roadmapTerm === currentRoadmap?.topic) {
|
||||
if (roadmapTerm === currentRoadmap?.term) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,7 +285,8 @@ export function GenerateRoadmap() {
|
||||
pageProgressMessage.set('Loading Roadmap');
|
||||
|
||||
const { response, error } = await httpGet<{
|
||||
topic: string;
|
||||
term: string;
|
||||
title: string;
|
||||
data: string;
|
||||
}>(`${import.meta.env.PUBLIC_API_URL}/v1-get-ai-roadmap/${roadmapId}`);
|
||||
|
||||
@@ -459,7 +460,7 @@ export function GenerateRoadmap() {
|
||||
{isLoggedInUser && !openAPIKey && (
|
||||
<button
|
||||
onClick={() => setIsConfiguring(true)}
|
||||
className="text-left rounded-xl border border-current px-2 py-0.5 text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
|
||||
className="rounded-xl border border-current px-2 py-0.5 text-left text-sm text-blue-500 transition-colors hover:bg-blue-400 hover:text-white"
|
||||
>
|
||||
By-pass all limits by{' '}
|
||||
<span className="font-semibold">
|
||||
|
||||
7
src/lib/number.ts
Normal file
7
src/lib/number.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const formatter = Intl.NumberFormat('en-US', {
|
||||
useGrouping: true,
|
||||
});
|
||||
|
||||
export function formatCommaNumber(number: number): string {
|
||||
return formatter.format(number);
|
||||
}
|
||||
Reference in New Issue
Block a user