Files
developer-roadmap/src/components/Projects/ProjectsList.tsx
T
Arik Chakma d5b9c97fed feat: add project's user count (#6992)
* feat: add project user count

* feat: add user count

* fix: user count
2024-09-04 18:22:15 +01:00

139 lines
4.3 KiB
TypeScript

import { ProjectCard } from './ProjectCard.tsx';
import { HeartHandshake, Trash2 } from 'lucide-react';
import { cn } from '../../lib/classname.ts';
import { useMemo, useState } from 'react';
import {
projectDifficulties,
type ProjectDifficultyType,
type ProjectFileType,
} from '../../lib/project.ts';
import {
deleteUrlParam,
getUrlParams,
setUrlParams,
} from '../../lib/browser.ts';
type DifficultyButtonProps = {
difficulty: ProjectDifficultyType;
isActive?: boolean;
onClick?: () => void;
};
function DifficultyButton(props: DifficultyButtonProps) {
const { difficulty, onClick, isActive } = props;
return (
<button
onClick={onClick}
className={cn(
'rounded-md border bg-white px-3 py-1 text-sm capitalize transition-colors hover:border-gray-300 hover:bg-gray-100',
{
'border-black bg-gray-100 hover:border-black hover:bg-gray-100 hover:text-black':
isActive,
},
)}
>
{difficulty}
</button>
);
}
type ProjectsListProps = {
projects: ProjectFileType[];
userCounts: Record<string, number>;
};
export function ProjectsList(props: ProjectsListProps) {
const { projects, userCounts } = props;
const { difficulty: urlDifficulty } = getUrlParams();
const [difficulty, setDifficulty] = useState<
ProjectDifficultyType | undefined
>(urlDifficulty);
const projectsByDifficulty: Map<ProjectDifficultyType, ProjectFileType[]> =
useMemo(() => {
const result = new Map<ProjectDifficultyType, ProjectFileType[]>();
for (const project of projects) {
const difficulty = project.frontmatter.difficulty;
if (!result.has(difficulty)) {
result.set(difficulty, []);
}
result.get(difficulty)?.push(project);
}
return result;
}, [projects]);
const matchingProjects = difficulty
? projectsByDifficulty.get(difficulty) || []
: projects;
return (
<div className="flex flex-col">
<div className="my-2.5 flex items-center justify-between">
<div className="flex flex-wrap gap-1">
{projectDifficulties.map((projectDifficulty) => (
<DifficultyButton
onClick={() => {
setDifficulty(projectDifficulty);
setUrlParams({ difficulty: projectDifficulty });
}}
difficulty={projectDifficulty}
isActive={projectDifficulty === difficulty}
/>
))}
{difficulty && (
<button
onClick={() => {
setDifficulty(undefined);
deleteUrlParam('difficulty');
}}
className="flex items-center gap-1.5 rounded-md border border-red-500 bg-transparent px-2 py-0.5 text-sm text-red-500 transition-colors hover:bg-red-500 hover:text-white"
>
<Trash2 className="h-3.5 w-3.5" strokeWidth={2.25} />
Clear
</button>
)}
</div>
<a
href={
'https://github.com/kamranahmedse/developer-roadmap/issues/new?assignees=&labels=project+contribution&projects=&template=05-project-contribution.yml'
}
target={'_blank'}
className="hidden items-center gap-2 rounded-md border border-transparent px-2 py-0.5 text-sm underline underline-offset-2 hover:bg-black hover:text-white hover:no-underline sm:flex"
>
<HeartHandshake className="h-4 w-4" />
Submit a Project Idea
</a>
</div>
<div className="mb-24 grid grid-cols-1 gap-1.5 sm:grid-cols-2 lg:grid-cols-3">
{matchingProjects.length === 0 && (
<div className="col-span-3 rounded-md border bg-white p-4 text-left text-sm text-gray-500">
<p>No matching projects found.</p>
</div>
)}
{matchingProjects
.sort((project) => {
return project.frontmatter.difficulty === 'beginner'
? -1
: project.frontmatter.difficulty === 'intermediate'
? 0
: 1;
})
.sort((a, b) => {
return a.frontmatter.sort - b.frontmatter.sort;
})
.map((matchingProject) => {
const count = userCounts[matchingProject?.id] || 0;
return <ProjectCard project={matchingProject} userCount={count} />;
})}
</div>
</div>
);
}