1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-25 16:39:02 +02:00

feat: add projects functionality for backend roadmap (#6412)

* Add empty projects listing and cards

* Projects page header

* Projects listing and filtering

* Add project detail page

* Add a task tracker CLI project

* UI changes

* Add projects for expense and task tracker

* Add new projects

* Add blogging platform API project

* Project page UI changes

* Add todo list api project

* Add todo list api project

* Add a project idea for weather service

* Add expense tracker API project

* Add more details to expense tracker

* Add markdown note taking app idea

* Add URL shortener service project

* Add broadcast server project

* Add ecommerce project idea

* Add project on other roadmaps

* Add workout tracker project

* Add image processing project idea

* Add movie reservation system ap

* Add movie reservation system project

* Add realtime leader board system project

* Add details for a database backup utility

* Add project contribution docs

* Fix UI of the discover page
This commit is contained in:
Kamran Ahmed
2024-08-07 16:35:19 +01:00
committed by GitHub
parent abeea1a5b5
commit ad3f28a8c4
32 changed files with 2180 additions and 16 deletions

View File

@@ -0,0 +1,133 @@
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[];
};
export function ProjectsList(props: ProjectsListProps) {
const { projects } = 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={''}
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) => (
<ProjectCard project={matchingProject} />
))}
</div>
</div>
);
}