feat: explore ai roadmaps

fix/ai-roadmap
Arik Chakma 9 months ago
parent 575e05d493
commit 56b327177b
  1. 183
      src/components/ExploreAIRoadmap/ExploreAIRoadmap.tsx
  2. 10
      src/pages/ai/explore.astro

@ -0,0 +1,183 @@
import { useEffect, useState, useCallback } from 'react';
import { useToast } from '../../hooks/use-toast';
import { httpGet } from '../../lib/http';
import { getRelativeTimeString } from '../../lib/date';
import {
BadgeCheck,
CalendarCheck,
Eye,
Loader2,
RefreshCcw,
Sparkles,
} from 'lucide-react';
export interface AIRoadmapDocument {
_id?: string;
topic: string;
data: string;
viewCount: number;
createdAt: Date;
updatedAt: Date;
}
type ExploreRoadmapsResponse = {
data: AIRoadmapDocument[];
totalCount: number;
totalPages: number;
currPage: number;
perPage: number;
};
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 loadAIRoadamps = useCallback(
async (currPage: number) => {
const { response, error } = await httpGet<ExploreRoadmapsResponse>(
`${import.meta.env.PUBLIC_API_URL}/v1-explore-roadmaps`,
{
currPage,
},
);
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;
}
setRoadmaps(newRoadmaps);
setCurrPage(response.currPage);
setTotalPages(response.totalPages);
},
[currPage, roadmaps],
);
useEffect(() => {
loadAIRoadamps(currPage).finally(() => {
setIsLoading(false);
});
}, []);
const hasMorePages = currPage < totalPages;
return (
<section className="container mx-auto">
<div className="border-b pb-8 pt-10">
<h2 className="text-base font-semibold text-gray-800 sm:text-lg">
Explore AI Generated Roadmaps
</h2>
<p className="mb-2.5 mt-2 text-balance text-sm text-gray-800 sm:mb-1.5 sm:mt-1 sm:text-base">
These roadmaps are generated by AI based on the data and the topic
community has provided. You can also create your own roadmap and share
it with the community.
</p>
<div className="flex flex-col items-start gap-2 sm:flex-row sm:items-center">
<a
href="/roadmaps"
className="inline-flex items-center gap-1.5 text-sm font-semibold text-gray-700 underline-offset-2 hover:underline"
>
<BadgeCheck className="h-4 w-4 stroke-[2.5]" />
Visit Official Roadmaps
</a>
<span className="hidden font-black text-gray-700 sm:block">
&middot;
</span>
<a
href="/ai"
className="inline-flex items-center gap-1.5 text-sm font-semibold text-blue-600 underline-offset-2 hover:underline"
>
<Sparkles className="h-4 w-4 stroke-[2.5]" />
Generate Your Own Roadmap
</a>
</div>
</div>
{isLoading ? (
<ul className="mt-8 grid grid-cols-3 gap-2">
{new Array(6).fill(0).map((_, index) => (
<li
key={index}
className="animate-pulse rounded-md border bg-gray-100"
>
<div className="h-10 w-full"></div>
<div className="flex items-center justify-between gap-2 border-t px-2.5 py-2">
<span className="flex items-center gap-1.5 text-sm text-gray-600">
<Eye size={15} className="inline-block" />
<span>{index} views</span>
</span>
<span className="flex items-center gap-1.5 text-sm text-gray-600">
<CalendarCheck size={15} className="inline-block" />
<span>{index}h ago</span>
</span>
</div>
</li>
))}
</ul>
) : (
<div className="mt-8">
{roadmaps?.length === 0 ? (
<div className="text-center text-gray-800">No roadmaps found</div>
) : (
<ul className="grid grid-cols-3 gap-2">
{roadmaps.map((roadmap) => (
<li key={roadmap._id} className="rounded-md border">
<h2
className="truncate px-2.5 py-2.5 text-xl font-medium leading-none tracking-wide"
title={roadmap.topic}
>
{roadmap.topic}
</h2>
<div className="flex items-center justify-between gap-2 border-t px-2.5 py-2">
<span className="flex items-center gap-1.5 text-sm text-gray-600">
<Eye size={15} className="inline-block" />
<span>{roadmap?.viewCount || 0} views</span>
</span>
<span className="flex items-center gap-1.5 text-sm text-gray-600">
<CalendarCheck size={15} className="inline-block" />
<span>
{getRelativeTimeString(String(roadmap?.createdAt))}
</span>
</span>
</div>
</li>
))}
{hasMorePages && (
<li>
<button
onClick={async () => {
setIsLoadingMore(true);
await loadAIRoadamps(currPage + 1);
setIsLoadingMore(false);
}}
className="flex h-full min-h-[79px] w-full items-center justify-center gap-1.5 rounded-md border bg-gray-100 font-medium text-gray-900 hover:bg-gray-100/80 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>
</li>
)}
</ul>
)}
</div>
)}
</section>
);
}

@ -0,0 +1,10 @@
---
import LoginPopup from '../../components/AuthenticationFlow/LoginPopup.astro';
import { ExploreAIRoadmap } from '../../components/ExploreAIRoadmap/ExploreAIRoadmap';
import AccountLayout from '../../layouts/AccountLayout.astro';
---
<AccountLayout title='Explore Roadmap AI'>
<ExploreAIRoadmap client:load />
<LoginPopup />
</AccountLayout>
Loading…
Cancel
Save