Merge branch 'feat/discover' of https://github.com/kamranahmedse/developer-roadmap into feat/discover

feat/discover
Arik Chakma 5 months ago
commit c2682e84ce
  1. 199
      src/components/DiscoverRoadmaps/DiscoverRoadmaps.tsx
  2. 2
      src/components/ExploreAIRoadmap/LoadingRoadmaps.tsx
  3. 10
      src/components/Rating/Rating.tsx

@ -10,6 +10,7 @@ import { LoadingRoadmaps } from '../ExploreAIRoadmap/LoadingRoadmaps';
import { httpGet } from '../../lib/http'; import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast'; import { useToast } from '../../hooks/use-toast';
import { DiscoverRoadmapSorting } from './DiscoverRoadmapSorting'; import { DiscoverRoadmapSorting } from './DiscoverRoadmapSorting';
import { CreateRoadmapModal } from '../CustomRoadmap/CreateRoadmap/CreateRoadmapModal.tsx';
type DiscoverRoadmapsProps = {}; type DiscoverRoadmapsProps = {};
@ -103,90 +104,132 @@ export function DiscoverRoadmaps(props: DiscoverRoadmapsProps) {
setRoadmapsResponse(response); setRoadmapsResponse(response);
}; };
const [isCreatingRoadmap, setIsCreatingRoadmap] = useState(false);
const roadmaps = roadmapsResponse?.data || []; const roadmaps = roadmapsResponse?.data || [];
const loadingIndicator = isLoading && <LoadingRoadmaps />; const loadingIndicator = isLoading && <LoadingRoadmaps />;
return ( return (
<section className="container mx-auto py-3 sm:py-6"> <>
<div className="mb-3.5 flex items-stretch justify-between gap-2.5"> {isCreatingRoadmap && (
<SearchRoadmap <CreateRoadmapModal
total={roadmapsResponse?.totalCount || 0} onClose={() => {
value={pageState.searchTerm} setIsCreatingRoadmap(false);
isLoading={isLoading} }}
onValueChange={(value) => {}} />
/> )}
<DiscoverRoadmapSorting <div className="border-b bg-white py-7">
sortBy={pageState.sortBy} <div className="container text-left">
onSortChange={(sortBy) => { <div className="flex flex-col items-start bg-white">
setPageState({ <h1 className="mb-1 text-2xl font-bold sm:text-3xl">
...pageState, Community Roadmaps
sortBy, </h1>
}); <p className="text-base text-gray-500">
}} Browse the roadmaps created by the community or{' '}
/> <button
</div> onClick={() => {
setIsCreatingRoadmap(true);
{loadingIndicator} }}
{roadmaps.length === 0 && !isLoading && <EmptyDiscoverRoadmaps />} className="rounded text-blue-600 underline"
{roadmaps.length > 0 && !isLoading && ( >
<> create your own roadmap
<ul className="mb-4 grid grid-cols-1 items-stretch gap-2 sm:grid-cols-2 lg:grid-cols-3"> </button>
{roadmaps.map((roadmap) => {
const roadmapLink = `/r/${roadmap.slug}`;
return (
<li key={roadmap._id} className="h-full">
<a
key={roadmap._id}
href={roadmapLink}
className="flex h-full flex-col rounded-md border transition-colors hover:bg-gray-100"
target={'_blank'}
>
<div className="grow">
<h2 className="mt-2.5 px-2.5 text-base font-medium leading-tight">
{roadmap.title}
</h2>
<p className="my-2.5 px-2.5 text-sm text-gray-500">
{roadmap.description}
</p> </p>
</div> </div>
</div>
<div className="flex items-center justify-between gap-2 px-2.5 py-2"> </div>
<span className="flex items-center gap-1.5 text-xs text-gray-400"> <div className="py-3 bg-gray-50">
<Shapes size={15} className="inline-block" /> <section className="container mx-auto py-3">
{Intl.NumberFormat('en-US', { <div className="mb-3.5 flex items-stretch justify-between gap-2.5">
notation: 'compact', <SearchRoadmap
}).format(roadmap.topicCount)}{' '} total={roadmapsResponse?.totalCount || 0}
topics value={pageState.searchTerm}
</span> isLoading={isLoading}
onValueChange={(value) => {
<Rating }}
rating={roadmap?.ratings?.average || 0} />
readOnly={true}
starSize={16} <DiscoverRoadmapSorting
sortBy={pageState.sortBy}
onSortChange={(sortBy) => {
setPageState({
...pageState,
sortBy,
});
}}
/> />
</div> </div>
</a>
</li> {loadingIndicator}
); {roadmaps.length === 0 && !isLoading && <EmptyDiscoverRoadmaps/>}
})} {roadmaps.length > 0 && !isLoading && (
</ul> <>
<ul className="mb-4 grid grid-cols-1 items-stretch gap-3 sm:grid-cols-2 lg:grid-cols-3">
<Pagination {roadmaps.map((roadmap) => {
currPage={roadmapsResponse?.currPage || 1} const roadmapLink = `/r/${roadmap.slug}`;
totalPages={roadmapsResponse?.totalPages || 1} const totalRatings = Object.keys(
perPage={roadmapsResponse?.perPage || 0} roadmap.ratings?.breakdown || [],
totalCount={roadmapsResponse?.totalCount || 0} ).reduce(
onPageChange={(page) => { (acc: number, key: string) =>
setPageState({ acc + roadmap.ratings.breakdown[key as any],
...pageState, 0,
currentPage: page, );
}); return (
}} <li key={roadmap._id} className="h-full min-h-[175px]">
/> <a
</> key={roadmap._id}
)} href={roadmapLink}
</section> className="flex h-full flex-col rounded-lg border bg-white p-3.5 transition-colors hover:border-gray-300 hover:bg-gray-50"
target={'_blank'}
>
<div className="grow">
<h2 className="text-balance text-base font-bold leading-tight">
{roadmap.title}
</h2>
<p className="mt-2 text-sm text-gray-500">
{roadmap.description}
</p>
</div>
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1 text-xs text-gray-400">
<Shapes size={15} className="inline-block"/>
{Intl.NumberFormat('en-US', {
notation: 'compact',
}).format(roadmap.topicCount)}{' '}
</span>
<Rating
rating={roadmap?.ratings?.average || 0}
readOnly={true}
starSize={16}
total={totalRatings}
/>
</div>
</a>
</li>
);
})}
</ul>
<Pagination
currPage={roadmapsResponse?.currPage || 1}
totalPages={roadmapsResponse?.totalPages || 1}
perPage={roadmapsResponse?.perPage || 0}
totalCount={roadmapsResponse?.totalCount || 0}
onPageChange={(page) => {
setPageState({
...pageState,
currentPage: page,
});
}}
/>
</>
)}
</section>
</div>
</>
); );
} }

@ -4,7 +4,7 @@ export function LoadingRoadmaps() {
{new Array(21).fill(0).map((_, index) => ( {new Array(21).fill(0).map((_, index) => (
<li <li
key={index} key={index}
className="h-[95px] animate-pulse rounded-md border bg-gray-100" className="h-[175px] animate-pulse rounded-md border bg-gray-100"
/> />
))} ))}
</ul> </ul>

@ -7,6 +7,7 @@ type RatingProps = {
starSize?: number; starSize?: number;
readOnly?: boolean; readOnly?: boolean;
className?: string; className?: string;
total?: number;
}; };
export function Rating(props: RatingProps) { export function Rating(props: RatingProps) {
@ -22,6 +23,10 @@ export function Rating(props: RatingProps) {
const starCount = Math.floor(stars); const starCount = Math.floor(stars);
const decimalWidthPercentage = Math.min((stars - starCount) * 100, 100); const decimalWidthPercentage = Math.min((stars - starCount) * 100, 100);
if (readOnly && starCount === 0) {
return <span className="text-xs text-gray-400">No ratings yet</span>;
}
return ( return (
<div className={cn('flex', className)}> <div className={cn('flex', className)}>
{[1, 2, 3, 4, 5].map((counter) => { {[1, 2, 3, 4, 5].map((counter) => {
@ -43,6 +48,11 @@ export function Rating(props: RatingProps) {
/> />
); );
})} })}
{props.total && (
<span className="ml-1.5 text-xs text-gray-400">
({Intl.NumberFormat('en-US').format(props.total)})
</span>
)}
</div> </div>
); );
} }

Loading…
Cancel
Save