mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-14 13:13:59 +02:00
feat: add project's user count (#6992)
* feat: add project user count * feat: add user count * fix: user count
This commit is contained in:
15
src/api/project.ts
Normal file
15
src/api/project.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { type APIContext } from 'astro';
|
||||||
|
import { api } from './api.ts';
|
||||||
|
|
||||||
|
export function projectApi(context: APIContext) {
|
||||||
|
return {
|
||||||
|
listProjectsUserCount: async function (projectIds: string[]) {
|
||||||
|
return api(context).post<Record<string, number>>(
|
||||||
|
`${import.meta.env.PUBLIC_API_URL}/v1-list-projects-user-count`,
|
||||||
|
{
|
||||||
|
projectIds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@@ -3,9 +3,11 @@ import type {
|
|||||||
ProjectDifficultyType,
|
ProjectDifficultyType,
|
||||||
ProjectFileType,
|
ProjectFileType,
|
||||||
} from '../../lib/project.ts';
|
} from '../../lib/project.ts';
|
||||||
|
import { Users } from 'lucide-react';
|
||||||
|
|
||||||
type ProjectCardProps = {
|
type ProjectCardProps = {
|
||||||
project: ProjectFileType;
|
project: ProjectFileType;
|
||||||
|
userCount?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const badgeVariants: Record<ProjectDifficultyType, string> = {
|
const badgeVariants: Record<ProjectDifficultyType, string> = {
|
||||||
@@ -15,7 +17,7 @@ const badgeVariants: Record<ProjectDifficultyType, string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ProjectCard(props: ProjectCardProps) {
|
export function ProjectCard(props: ProjectCardProps) {
|
||||||
const { project } = props;
|
const { project, userCount = 0 } = props;
|
||||||
|
|
||||||
const { frontmatter, id } = project;
|
const { frontmatter, id } = project;
|
||||||
|
|
||||||
@@ -33,6 +35,10 @@ export function ProjectCard(props: ProjectCardProps) {
|
|||||||
</span>
|
</span>
|
||||||
<span className="mb-1 mt-2.5 font-medium">{frontmatter.title}</span>
|
<span className="mb-1 mt-2.5 font-medium">{frontmatter.title}</span>
|
||||||
<span className="text-sm text-gray-500">{frontmatter.description}</span>
|
<span className="text-sm text-gray-500">{frontmatter.description}</span>
|
||||||
|
<span className="mt-2.5 flex items-center gap-2 text-xs text-gray-500">
|
||||||
|
<Users className="inline-block size-3.5" />
|
||||||
|
{userCount > 0 ? <>{userCount} Started</> : <>Be the first to solve!</>}
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -40,10 +40,11 @@ function DifficultyButton(props: DifficultyButtonProps) {
|
|||||||
|
|
||||||
type ProjectsListProps = {
|
type ProjectsListProps = {
|
||||||
projects: ProjectFileType[];
|
projects: ProjectFileType[];
|
||||||
|
userCounts: Record<string, number>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ProjectsList(props: ProjectsListProps) {
|
export function ProjectsList(props: ProjectsListProps) {
|
||||||
const { projects } = props;
|
const { projects, userCounts } = props;
|
||||||
|
|
||||||
const { difficulty: urlDifficulty } = getUrlParams();
|
const { difficulty: urlDifficulty } = getUrlParams();
|
||||||
const [difficulty, setDifficulty] = useState<
|
const [difficulty, setDifficulty] = useState<
|
||||||
@@ -127,9 +128,10 @@ export function ProjectsList(props: ProjectsListProps) {
|
|||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return a.frontmatter.sort - b.frontmatter.sort;
|
return a.frontmatter.sort - b.frontmatter.sort;
|
||||||
})
|
})
|
||||||
.map((matchingProject) => (
|
.map((matchingProject) => {
|
||||||
<ProjectCard project={matchingProject} />
|
const count = userCounts[matchingProject?.id] || 0;
|
||||||
))}
|
return <ProjectCard project={matchingProject} userCount={count} />;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -1,25 +1,12 @@
|
|||||||
---
|
---
|
||||||
import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap';
|
|
||||||
import FAQs, { type FAQType } from '../../components/FAQs/FAQs.astro';
|
|
||||||
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
|
|
||||||
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
|
|
||||||
import RoadmapHeader from '../../components/RoadmapHeader.astro';
|
import RoadmapHeader from '../../components/RoadmapHeader.astro';
|
||||||
import { FolderKanbanIcon } from 'lucide-react';
|
|
||||||
import { EmptyProjects } from '../../components/Projects/EmptyProjects';
|
import { EmptyProjects } from '../../components/Projects/EmptyProjects';
|
||||||
import { ProjectsList } from '../../components/Projects/ProjectsList';
|
import { ProjectsList } from '../../components/Projects/ProjectsList';
|
||||||
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro';
|
|
||||||
import { UserProgressModal } from '../../components/UserProgress/UserProgressModal';
|
|
||||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
import { getProjectsByRoadmapId } from '../../lib/project';
|
import { getProjectsByRoadmapId } from '../../lib/project';
|
||||||
import {
|
|
||||||
generateArticleSchema,
|
|
||||||
generateFAQSchema,
|
|
||||||
} from '../../lib/jsonld-schema';
|
|
||||||
import { getOpenGraphImageUrl } from '../../lib/open-graph';
|
import { getOpenGraphImageUrl } from '../../lib/open-graph';
|
||||||
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
|
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
|
||||||
import RoadmapNote from '../../components/RoadmapNote.astro';
|
import { projectApi } from '../../api/project';
|
||||||
import { RoadmapTitleQuestion } from '../../components/RoadmapTitleQuestion';
|
|
||||||
import ResourceProgressStats from '../../components/ResourceProgressStats.astro';
|
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const roadmapIds = await getRoadmapIds();
|
const roadmapIds = await getRoadmapIds();
|
||||||
@@ -48,7 +35,7 @@ const ogImageUrl =
|
|||||||
resourceId: roadmapId,
|
resourceId: roadmapId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const descriptionNoun = {
|
const descriptionNoun: Record<string, string> = {
|
||||||
'AI and Data Scientist': 'AI and Data Science',
|
'AI and Data Scientist': 'AI and Data Science',
|
||||||
'Game Developer': 'Game Development',
|
'Game Developer': 'Game Development',
|
||||||
'Technical Writer': 'Technical Writing',
|
'Technical Writer': 'Technical Writing',
|
||||||
@@ -61,10 +48,15 @@ const description = `Project ideas to take you from beginner to advanced in ${de
|
|||||||
// `Seeking backend projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`
|
// `Seeking backend projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`
|
||||||
const seoTitle = `${roadmapData.briefTitle} Projects`;
|
const seoTitle = `${roadmapData.briefTitle} Projects`;
|
||||||
const nounTitle =
|
const nounTitle =
|
||||||
descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle;
|
descriptionNoun[roadmapData?.briefTitle] || roadmapData.briefTitle;
|
||||||
const seoDescription = `Seeking ${nounTitle.toLowerCase()} projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`;
|
const seoDescription = `Seeking ${nounTitle.toLowerCase()} projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`;
|
||||||
|
|
||||||
const projects = await getProjectsByRoadmapId(roadmapId);
|
const projects = await getProjectsByRoadmapId(roadmapId);
|
||||||
|
const projectIds = projects.map((project) => project.id);
|
||||||
|
|
||||||
|
const projectApiClient = projectApi(Astro);
|
||||||
|
const { response: userCounts } =
|
||||||
|
await projectApiClient.listProjectsUserCount(projectIds);
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
@@ -95,7 +87,15 @@ const projects = await getProjectsByRoadmapId(roadmapId);
|
|||||||
|
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
{projects.length === 0 && <EmptyProjects client:load />}
|
{projects.length === 0 && <EmptyProjects client:load />}
|
||||||
{projects.length > 0 && <ProjectsList projects={projects} client:load />}
|
{
|
||||||
|
projects.length > 0 && (
|
||||||
|
<ProjectsList
|
||||||
|
projects={projects}
|
||||||
|
userCounts={userCounts || {}}
|
||||||
|
client:load
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
Reference in New Issue
Block a user