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

feat/discover
Arik Chakma 5 months ago
commit c2682e84ce
  1. 63
      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,18 +104,51 @@ 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"> <>
{isCreatingRoadmap && (
<CreateRoadmapModal
onClose={() => {
setIsCreatingRoadmap(false);
}}
/>
)}
<div className="border-b bg-white py-7">
<div className="container text-left">
<div className="flex flex-col items-start bg-white">
<h1 className="mb-1 text-2xl font-bold sm:text-3xl">
Community Roadmaps
</h1>
<p className="text-base text-gray-500">
Browse the roadmaps created by the community or{' '}
<button
onClick={() => {
setIsCreatingRoadmap(true);
}}
className="rounded text-blue-600 underline"
>
create your own roadmap
</button>
</p>
</div>
</div>
</div>
<div className="py-3 bg-gray-50">
<section className="container mx-auto py-3">
<div className="mb-3.5 flex items-stretch justify-between gap-2.5"> <div className="mb-3.5 flex items-stretch justify-between gap-2.5">
<SearchRoadmap <SearchRoadmap
total={roadmapsResponse?.totalCount || 0} total={roadmapsResponse?.totalCount || 0}
value={pageState.searchTerm} value={pageState.searchTerm}
isLoading={isLoading} isLoading={isLoading}
onValueChange={(value) => {}} onValueChange={(value) => {
}}
/> />
<DiscoverRoadmapSorting <DiscoverRoadmapSorting
@ -132,39 +166,46 @@ export function DiscoverRoadmaps(props: DiscoverRoadmapsProps) {
{roadmaps.length === 0 && !isLoading && <EmptyDiscoverRoadmaps/>} {roadmaps.length === 0 && !isLoading && <EmptyDiscoverRoadmaps/>}
{roadmaps.length > 0 && !isLoading && ( {roadmaps.length > 0 && !isLoading && (
<> <>
<ul className="mb-4 grid grid-cols-1 items-stretch gap-2 sm:grid-cols-2 lg:grid-cols-3"> <ul className="mb-4 grid grid-cols-1 items-stretch gap-3 sm:grid-cols-2 lg:grid-cols-3">
{roadmaps.map((roadmap) => { {roadmaps.map((roadmap) => {
const roadmapLink = `/r/${roadmap.slug}`; const roadmapLink = `/r/${roadmap.slug}`;
const totalRatings = Object.keys(
roadmap.ratings?.breakdown || [],
).reduce(
(acc: number, key: string) =>
acc + roadmap.ratings.breakdown[key as any],
0,
);
return ( return (
<li key={roadmap._id} className="h-full"> <li key={roadmap._id} className="h-full min-h-[175px]">
<a <a
key={roadmap._id} key={roadmap._id}
href={roadmapLink} href={roadmapLink}
className="flex h-full flex-col rounded-md border transition-colors hover:bg-gray-100" 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'} target={'_blank'}
> >
<div className="grow"> <div className="grow">
<h2 className="mt-2.5 px-2.5 text-base font-medium leading-tight"> <h2 className="text-balance text-base font-bold leading-tight">
{roadmap.title} {roadmap.title}
</h2> </h2>
<p className="my-2.5 px-2.5 text-sm text-gray-500"> <p className="mt-2 text-sm text-gray-500">
{roadmap.description} {roadmap.description}
</p> </p>
</div> </div>
<div className="flex items-center justify-between gap-2 px-2.5 py-2"> <div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1.5 text-xs text-gray-400"> <span className="flex items-center gap-1 text-xs text-gray-400">
<Shapes size={15} className="inline-block"/> <Shapes size={15} className="inline-block"/>
{Intl.NumberFormat('en-US', { {Intl.NumberFormat('en-US', {
notation: 'compact', notation: 'compact',
}).format(roadmap.topicCount)}{' '} }).format(roadmap.topicCount)}{' '}
topics
</span> </span>
<Rating <Rating
rating={roadmap?.ratings?.average || 0} rating={roadmap?.ratings?.average || 0}
readOnly={true} readOnly={true}
starSize={16} starSize={16}
total={totalRatings}
/> />
</div> </div>
</a> </a>
@ -188,5 +229,7 @@ export function DiscoverRoadmaps(props: DiscoverRoadmapsProps) {
</> </>
)} )}
</section> </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