Add project detail page

feat/projects-list
Kamran Ahmed 4 months ago
parent 9355c1e770
commit 51dd3c48ba
  1. 1
      src/components/Badge.tsx
  2. 1
      src/components/Projects/ProjectCard.tsx
  3. 14
      src/lib/project.ts
  4. 95
      src/pages/projects/[projectId].astro

@ -13,6 +13,7 @@ export function Badge(type: BadgeProps) {
yellow: 'bg-yellow-100 text-yellow-700 border-yellow-200', yellow: 'bg-yellow-100 text-yellow-700 border-yellow-200',
grey: 'bg-gray-100 text-gray-700 border-gray-200', grey: 'bg-gray-100 text-gray-700 border-gray-200',
white: 'bg-white text-black border-gray-200', white: 'bg-white text-black border-gray-200',
teal: 'bg-teal-100 text-teal-700 border-teal-200',
}; };
return ( return (

@ -23,6 +23,7 @@ export function ProjectCard(props: ProjectCardProps) {
<a <a
href={`/projects/${id}`} href={`/projects/${id}`}
className="flex flex-col rounded-md border bg-white p-3 transition-colors hover:border-gray-300 hover:bg-gray-50" className="flex flex-col rounded-md border bg-white p-3 transition-colors hover:border-gray-300 hover:bg-gray-50"
target="_blank"
> >
<span className="flex justify-between gap-1.5"> <span className="flex justify-between gap-1.5">
<Badge <Badge

@ -1,6 +1,11 @@
import type { MarkdownFileType } from './file'; import type { MarkdownFileType } from './file';
import { getRoadmapById, type RoadmapFileType } from './roadmap.ts';
export const projectDifficulties = ['beginner', 'intermediate', 'advanced'] as const; export const projectDifficulties = [
'beginner',
'intermediate',
'advanced',
] as const;
export type ProjectDifficultyType = (typeof projectDifficulties)[number]; export type ProjectDifficultyType = (typeof projectDifficulties)[number];
export interface ProjectFrontmatter { export interface ProjectFrontmatter {
@ -14,12 +19,14 @@ export interface ProjectFrontmatter {
title: string; title: string;
description: string; description: string;
keywords: string[]; keywords: string[];
ogImageUrl: string;
}; };
roadmapIds: string[]; roadmapIds: string[];
} }
export type ProjectFileType = MarkdownFileType<ProjectFrontmatter> & { export type ProjectFileType = MarkdownFileType<ProjectFrontmatter> & {
id: string; id: string;
roadmaps: RoadmapFileType[];
}; };
/** /**
@ -74,9 +81,14 @@ export async function getProjectById(
groupId: string, groupId: string,
): Promise<ProjectFileType> { ): Promise<ProjectFileType> {
const project = await import(`../data/projects/${groupId}.md`); const project = await import(`../data/projects/${groupId}.md`);
const roadmapIds = project.frontmatter.roadmapIds || [];
const roadmaps = await Promise.all(
roadmapIds.map((roadmapId: string) => getRoadmapById(roadmapId)),
);
return { return {
...project, ...project,
roadmaps: roadmaps,
id: projectPathToId(project.file), id: projectPathToId(project.file),
}; };
} }

@ -0,0 +1,95 @@
---
import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import RelatedRoadmaps from '../../components/RelatedRoadmaps.astro';
import RoadmapHeader from '../../components/RoadmapHeader.astro';
import ShareIcons from '../../components/ShareIcons/ShareIcons.astro';
import { TopicDetail } from '../../components/TopicDetail/TopicDetail';
import { UserProgressModal } from '../../components/UserProgress/UserProgressModal';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { Badge } from '../../components/Badge';
import {
generateArticleSchema,
generateFAQSchema,
} from '../../lib/jsonld-schema';
import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import RoadmapNote from '../../components/RoadmapNote.astro';
import { RoadmapTitleQuestion } from '../../components/RoadmapTitleQuestion';
import ResourceProgressStats from '../../components/ResourceProgressStats.astro';
import {
getAllProjects,
getProjectById,
getProjectsByRoadmapId,
ProjectFrontmatter,
} from '../../lib/project';
import AstroIcon from '../../components/AstroIcon.astro';
export async function getStaticPaths() {
const projects = await getAllProjects();
return projects
.map((project) => project.id)
.map((projectId) => ({
params: { projectId },
}));
}
interface Params extends Record<string, string | undefined> {
projectId: string;
}
const { projectId } = Astro.params as Params;
const project = await getProjectById(projectId);
const projectData = project.frontmatter as ProjectFrontmatter;
let jsonLdSchema = [];
const ogImageUrl = projectData?.seo?.ogImageUrl || '/images/og-img.png';
---
<BaseLayout
permalink={`/projects/${projectId}`}
title={projectData?.seo?.title}
briefTitle={projectData.title}
ogImageUrl={ogImageUrl}
description={projectData.seo.description}
keywords={projectData.seo.keywords}
jsonLd={jsonLdSchema}
resourceId={projectId}
resourceType='project'
>
<div class='bg-gray-50'>
<div class='container'>
<div
class='my-3 flex flex-row items-center gap-1.5 rounded-md border bg-white px-2 py-2 text-sm font-medium'
>
<AstroIcon icon='map' class='h-4 w-4' />
Relevant roadmaps to visit <span class='flex flex-row gap-1'>
{
project.roadmaps.map((roadmap) => (
<a
class='text-blue-700 underline underline-offset-2 hover:text-blue-800'
href={`/${roadmap.id}`}
>
{roadmap.frontmatter?.briefTitle}
</a>
))
}
</span>
</div>
<div class='mb-3 overflow-hidden rounded-md border bg-white p-5'>
<div class='-m-5 p-4'>
<div class='mb-2 flex flex-row gap-1.5'>
<Badge variant='yellow' text={projectData.difficulty} />
<Badge variant='blue' text={projectData.nature} />
</div>
<h1 class='text-2xl font-semibold'>{projectData.title}</h1>
<p class='text-gray-500'>{projectData.description}</p>
</div>
</div>
</div>
</div>
</BaseLayout>
Loading…
Cancel
Save